function stringValue(value: any, fallback = "") {
    const text = String(value ?? fallback).trim();
    return text || String(fallback);
}

function numberValue(value: any, fallback = 0) {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : fallback;
}

function boolValue(value: any) {
    return value === true || value === 1 || String(value || "") === "1";
}

export function createGridQuery(input: Record<string, any> = {}) {
    const q = stringValue(input.q ?? input.query, "*") || "*";
    const hasResolutionCompare = Object.prototype.hasOwnProperty.call(input, "resolutionCompare");
    const rawResolutionCompare = hasResolutionCompare
        ? String(input.resolutionCompare ?? "").trim().toLowerCase()
        : "gte";
    const resolutionCompare = rawResolutionCompare === "lte"
        ? "lte"
        : rawResolutionCompare
          ? "gte"
          : "";

    return Object.freeze({
        scope: stringValue(input.scope, "output").toLowerCase(),
        q,
        query: q,
        customRootId: stringValue(input.customRootId),
        subfolder: stringValue(input.subfolder),
        collectionId: stringValue(input.collectionId),
        viewScope: stringValue(input.viewScope),
        kind: stringValue(input.kind).toLowerCase(),
        workflowOnly: boolValue(input.workflowOnly),
        minRating: numberValue(input.minRating, 0),
        minSizeMB: numberValue(input.minSizeMB, 0),
        maxSizeMB: numberValue(input.maxSizeMB, 0),
        resolutionCompare,
        minWidth: numberValue(input.minWidth, 0),
        minHeight: numberValue(input.minHeight, 0),
        maxWidth: numberValue(input.maxWidth, 0),
        maxHeight: numberValue(input.maxHeight, 0),
        workflowType: stringValue(input.workflowType).toUpperCase(),
        workflowId: stringValue(input.workflowId),
        workflowModel: stringValue(input.workflowModel),
        workflowRunsOn: stringValue(input.workflowRunsOn).toLowerCase(),
        dateRange: stringValue(input.dateRange).toLowerCase(),
        dateExact: stringValue(input.dateExact),
        metadataSearchMode: stringValue(input.metadataSearchMode),
        sort: stringValue(input.sort, "mtime_desc").toLowerCase(),
        semanticMode:
            input.semanticMode === undefined || input.semanticMode === null
                ? undefined
                : boolValue(input.semanticMode),
        groupStacks:
            input.groupStacks === undefined || input.groupStacks === null
                ? undefined
                : boolValue(input.groupStacks),
    });
}

export function readGridQueryFromDataset(dataset: Record<string, any> = {}, overrides: Record<string, any> = {}) {
    return createGridQuery({
        scope: dataset.mjrScope,
        q: dataset.mjrQuery,
        customRootId: dataset.mjrCustomRootId,
        subfolder: dataset.mjrSubfolder,
        collectionId: dataset.mjrCollectionId,
        viewScope: dataset.mjrViewScope,
        kind: dataset.mjrFilterKind,
        workflowOnly: dataset.mjrFilterWorkflowOnly,
        minRating: dataset.mjrFilterMinRating,
        minSizeMB: dataset.mjrFilterMinSizeMB,
        maxSizeMB: dataset.mjrFilterMaxSizeMB,
        resolutionCompare: dataset.mjrFilterResolutionCompare ?? "gte",
        minWidth: dataset.mjrFilterMinWidth,
        minHeight: dataset.mjrFilterMinHeight,
        maxWidth: dataset.mjrFilterMaxWidth,
        maxHeight: dataset.mjrFilterMaxHeight,
        workflowType: dataset.mjrFilterWorkflowType,
        workflowId: dataset.mjrFilterWorkflowId,
        workflowModel: dataset.mjrFilterWorkflowModel,
        workflowRunsOn: dataset.mjrFilterWorkflowRunsOn,
        dateRange: dataset.mjrFilterDateRange,
        dateExact: dataset.mjrFilterDateExact,
        metadataSearchMode: dataset.mjrMetadataMode,
        sort: dataset.mjrSort,
        semanticMode: dataset.mjrSemanticMode,
        groupStacks: dataset.mjrGroupStacks,
        ...overrides,
    });
}

export function readGridQueryFromElement(element: any = null, overrides: Record<string, any> = {}) {
    return readGridQueryFromDataset(element?.dataset || {}, overrides);
}

export function readGridQueryText(element: HTMLElement | null = null, fallback = "*"): string {
    const dataset = element?.dataset || {};
    return readGridQueryFromDataset(dataset, {
        q: dataset.mjrQuery ?? fallback,
    }).q;
}

export function readGridSortKey(element: HTMLElement | null = null, fallback = "mtime_desc"): string {
    const dataset = element?.dataset || {};
    return readGridQueryFromDataset(dataset, {
        sort: dataset.mjrSort ?? fallback,
    }).sort;
}

export function gridQueryKey(query: Record<string, any> = {}): string {
    const normalized = createGridQuery(query);
    return [
        normalized.scope,
        normalized.q,
        normalized.sort,
        normalized.customRootId,
        normalized.subfolder,
        normalized.collectionId,
        normalized.viewScope,
        normalized.kind,
        normalized.workflowOnly ? "1" : "",
        normalized.minRating || "",
        normalized.minSizeMB || "",
        normalized.maxSizeMB || "",
        normalized.resolutionCompare,
        normalized.minWidth || "",
        normalized.minHeight || "",
        normalized.maxWidth || "",
        normalized.maxHeight || "",
        normalized.workflowType,
        normalized.workflowId,
        normalized.workflowModel,
        normalized.workflowRunsOn,
        normalized.dateRange,
        normalized.dateExact,
        normalized.semanticMode === undefined ? "" : normalized.semanticMode ? "1" : "0",
        normalized.groupStacks === undefined ? "" : normalized.groupStacks ? "1" : "0",
    ].join("|");
}

export function gridListQueryKey(query: Record<string, any> = {}): string {
    const normalized = createGridQuery(query);
    return [
        normalized.scope,
        normalized.q,
        normalized.sort,
        normalized.customRootId,
        normalized.subfolder,
        normalized.collectionId,
        normalized.kind,
        normalized.workflowOnly ? "1" : "",
        normalized.minRating || "",
        normalized.minSizeMB || "",
        normalized.maxSizeMB || "",
        normalized.resolutionCompare,
        normalized.minWidth || "",
        normalized.minHeight || "",
        normalized.maxWidth || "",
        normalized.maxHeight || "",
        normalized.workflowType,
        normalized.workflowId,
        normalized.workflowModel,
        normalized.workflowRunsOn,
        normalized.dateRange,
        normalized.dateExact,
    ].join("|");
}

export function gridQueryHasActiveFilters(query: Record<string, any> = {}): boolean {
    const normalized = createGridQuery(query);
    const viewScope = normalized.viewScope && normalized.viewScope !== normalized.scope
        ? normalized.viewScope
        : "";
    return !!(
        normalized.subfolder ||
        normalized.customRootId ||
        normalized.collectionId ||
        viewScope ||
        normalized.kind ||
        normalized.workflowOnly ||
        normalized.minRating > 0 ||
        normalized.minSizeMB > 0 ||
        normalized.maxSizeMB > 0 ||
        normalized.minWidth > 0 ||
        normalized.minHeight > 0 ||
        normalized.maxWidth > 0 ||
        normalized.maxHeight > 0 ||
        normalized.workflowType ||
        normalized.workflowId ||
        normalized.workflowModel ||
        normalized.workflowRunsOn ||
        normalized.dateRange ||
        normalized.dateExact
    );
}

export function isDefaultOutputBrowseQuery(query: Record<string, any> = {}): boolean {
    const normalized = createGridQuery(query);
    return (
        normalized.scope === "output" &&
        normalized.q === "*" &&
        normalized.sort === "mtime_desc" &&
        !gridQueryHasActiveFilters(normalized)
    );
}
