"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import { aggregateTableStat } from "@/lib/api/stats";
import type { OptionFilter } from "@/lib/registry/components/input/use-option-loader";
import type { StatItemDataSource, StatsItem } from "./types";

/**
 * use-stats-data — Phase G.1 + G.4.x preview-in-design
 *
 * Stats 항목 중 `dataSource.tableName` 이 설정된 것만 백엔드 `/aggregate` 로 값을
 * 가져온다. **디자인 모드에서도 미리보기 fetch** 함 (사용자가 카드 설정 즉시 결과
 * 확인할 수 있도록). 다만 typing 폭주 방지를 위해 350ms debounce.
 *
 *   - 키: `item.id ?? String(idx)`
 *   - request key 캐시 (같은 호출이 중복으로 가지 않음)
 *   - race guard: 새 effect 가 시작되면 이전 결과 무시
 *   - 카드 단위 error state: 한 카드 실패가 다른 카드를 죽이지 않음
 *   - debounce: 디자인 모드 350ms, 운영 모드 0ms (즉시)
 *
 * formData / userContext 가 들어오면 `OptionFilter.value_type` 의 `field` / `user`
 * 를 실제 값으로 치환한 후 백엔드에 보낸다.
 */

const DESIGN_DEBOUNCE_MS = 350;

export interface UseStatsDataOptions {
  isDesignMode?: boolean;
  formData?: Record<string, unknown>;
  userContext?: {
    companyCode?: string;
    userId?: string;
    deptCode?: string;
    userName?: string;
  };
  /** 외부에서 강제 재조회 트리거 (refreshKey 변경 시 모든 항목 다시 fetch) */
  refreshKey?: number | string;
}

export interface StatsValueState {
  value?: number;
  error?: string;
  loading?: boolean;
}

export type StatsDataMap = Record<string, StatsValueState>;

type StatsFetchPlanItem =
  | { key: string; skip: true; pending?: boolean }
  | {
      key: string;
      skip: false;
      reqKey: string;
      req: {
        tableName: string;
        aggregation: NonNullable<StatItemDataSource["aggregation"]>;
        columnName?: string;
        filters: Array<{ column: string; operator: string; value?: unknown }>;
      };
    };

const ITEM_KEY = (item: StatsItem, idx: number): string =>
  item.id != null ? String(item.id) : String(idx);

/**
 * OptionFilter 의 value_type 을 formData / userContext 로 치환해서 백엔드용 필터로
 * 변환. 치환에 실패한 항목은 (필수 컨텍스트 누락) drop 한다 — 의도와 다른 전체 결과를
 * 내는 것보다 카드 에러가 안전.
 */
function resolveFilters(
  filters: OptionFilter[] | undefined,
  formData: Record<string, unknown> | undefined,
  userContext: UseStatsDataOptions["userContext"],
): { resolved: Array<{ column: string; operator: string; value?: unknown }>; ok: boolean } {
  if (!filters || filters.length === 0) return { resolved: [], ok: true };

  const out: Array<{ column: string; operator: string; value?: unknown }> = [];
  for (const f of filters) {
    if (!f || !f.column) continue;
    const op = f.operator ?? "=";

    if (op === "isNull" || op === "isNotNull") {
      out.push({ column: f.column, operator: op });
      continue;
    }

    let value: unknown = f.value;
    const valueType = f.value_type ?? "static";
    if (valueType === "field") {
      const ref = f.field_ref;
      if (!ref) return { resolved: [], ok: false };
      value = formData?.[ref];
      if (value === undefined) return { resolved: [], ok: false };
    } else if (valueType === "user") {
      const uf = f.user_field;
      if (!uf) return { resolved: [], ok: false };
      value = userContext?.[uf];
      if (value === undefined || value === "") return { resolved: [], ok: false };
    }

    // value 가 명시적으로 null / undefined 면 비교 필터는 의미 없음 → skip
    // (isNull / isNotNull 은 위에서 이미 처리됨)
    if (value === undefined || value === null) {
      continue;
    }

    out.push({ column: f.column, operator: op, value });
  }
  return { resolved: out, ok: true };
}

/**
 * 항목의 fetch request key 를 만든다 — 같은 키면 캐시.
 */
function buildRequestKey(
  ds: StatItemDataSource,
  filters: Array<{ column: string; operator: string; value?: unknown }>,
): string {
  return JSON.stringify({
    t: ds.tableName,
    a: ds.aggregation ?? "count",
    c: ds.columnName ?? "",
    f: filters,
  });
}

/**
 * dataSource 가 fetch 가능한 상태인지 (tableName + 필수 columnName) 검사.
 */
function isDataSourceReady(ds: StatItemDataSource | undefined): boolean {
  if (!ds || !ds.tableName) return false;
  const agg = ds.aggregation ?? "count";
  if (agg === "count") return true;
  return !!ds.columnName;
}

export function useStatsData(
  items: StatsItem[],
  opts: UseStatsDataOptions = {},
): StatsDataMap {
  const { isDesignMode, formData, userContext, refreshKey } = opts;
  const [state, setState] = useState<StatsDataMap>({});

  // 요청 키 직렬화 — 항목 / formData / userContext / refreshKey 가 의미 있게 바뀔
  // 때만 effect 재실행. items 객체 동등성 X.
  const planSig = useMemo(() => {
    const plan: StatsFetchPlanItem[] = items.map((it, idx) => {
      const key = ITEM_KEY(it, idx);
      if (!isDataSourceReady(it.dataSource)) return { key, skip: true };
      const { resolved, ok } = resolveFilters(
        it.dataSource!.filters,
        formData,
        userContext,
      );
      if (!ok) return { key, skip: true, pending: true };
      const reqKey = buildRequestKey(it.dataSource!, resolved);
      return {
        key,
        skip: false,
        reqKey,
        req: {
          tableName: it.dataSource!.tableName!,
          aggregation: it.dataSource!.aggregation ?? "count",
          columnName: it.dataSource!.columnName,
          filters: resolved,
        },
      };
    });
    return JSON.stringify(plan);
  }, [items, formData, userContext]);

  const cacheRef = useRef<Map<string, number>>(new Map());
  const versionRef = useRef(0);

  useEffect(() => {
    const plan: StatsFetchPlanItem[] = JSON.parse(planSig);
    if (plan.every((p) => p.skip)) {
      setState((prev) => (Object.keys(prev).length > 0 ? {} : prev));
      return;
    }

    versionRef.current += 1;
    const myVersion = versionRef.current;
    let cancelled = false;

    if (refreshKey !== undefined) {
      cacheRef.current.clear();
    }

    // loading 표시 (skip 항목 제외, 캐시 hit 은 즉시 값으로)
    setState((prev) => {
      const next: StatsDataMap = { ...prev };
      let changed = false;
      for (const p of plan) {
        if (p.skip) {
          if (p.pending && next[p.key]) {
            delete next[p.key];
            changed = true;
          }
          continue;
        }
        const cached = cacheRef.current.get(p.reqKey);
        if (cached !== undefined && refreshKey === undefined) {
          if (next[p.key]?.value !== cached || next[p.key]?.loading || next[p.key]?.error) {
            next[p.key] = { value: cached };
            changed = true;
          }
        } else {
          if (!next[p.key]?.loading || next[p.key]?.error) {
            next[p.key] = { ...next[p.key], loading: true, error: undefined };
            changed = true;
          }
        }
      }
      return changed ? next : prev;
    });

    // 디자인 모드는 typing 폭주 방지용 debounce. 운영 모드는 즉시.
    const debounceMs = isDesignMode ? DESIGN_DEBOUNCE_MS : 0;
    const timer = setTimeout(() => {
      (async () => {
        for (const p of plan) {
          if (p.skip) continue;
          const cached = cacheRef.current.get(p.reqKey);
          if (cached !== undefined && refreshKey === undefined) continue;

          try {
            const { value } = await aggregateTableStat(p.req.tableName, {
              aggregation: p.req.aggregation,
              columnName: p.req.columnName,
              filters: p.req.filters as any,
            });
            if (cancelled || versionRef.current !== myVersion) return;
            cacheRef.current.set(p.reqKey, value);
            setState((prev) => ({
              ...prev,
              [p.key]: { value, loading: false },
            }));
          } catch (err: any) {
            if (cancelled || versionRef.current !== myVersion) return;
            const msg = err?.response?.data?.message || err?.message || "집계 실패";
            setState((prev) => ({
              ...prev,
              [p.key]: { ...prev[p.key], loading: false, error: String(msg) },
            }));
          }
        }
      })();
    }, debounceMs);

    return () => {
      cancelled = true;
      clearTimeout(timer);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [planSig, isDesignMode, refreshKey]);

  return state;
}
