'use client';

import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import type {
  BlockV2,
  CanvasV2,
  FieldConfig,
  Template,
  ViewV2,
} from '@/types/invyone-component';
import {
  allocateRowHeightsPx,
  classifyRows,
  computeLayout,
  toTrackTemplate,
} from '@/lib/layout/lineLayout';
import { ensureV2Views } from '@/lib/utils/templateMigrate';
import { BlockRenderer } from './BlockRenderer';
export { BlockRenderer } from './BlockRenderer';

/**
 * TemplateRenderer — INVYONE 템플릿 렌더러
 * ============================================================================
 *
 * 입력: Template. views 는 정규 모델(% 좌표 + role + responsivePolicy + bandId).
 * 레거시 absolute 포맷은 ensureV2Views 에서 1회 normalize 후 같은 파이프라인.
 *
 * 렌더 모델:
 *   자유배치 좌표(xPct/yPct/wPct/hPct)에서 세로/가로 line 을 추출하고,
 *   variable grid track 으로 재구성해 CSS Grid 로 렌더한다.
 *
 * 원칙:
 *   - 좌표가 진실이고, role 은 semantic marker 로만 사용한다.
 *   - 일반 블록은 grid span 으로 렌더한다.
 *   - role='overlay' 만 absolute overlay 예외 경로로 빠진다.
 *   - 버튼류는 실제 runtime cell 크기 안으로만 clamp 하여 clipping 을 막는다.
 * ============================================================================
 */

export type ViewKey = 'list' | 'create' | 'edit';

export interface TemplateRenderContext {
  fields: FieldConfig[];
  data: Record<string, any>[];
  loading: boolean;
  primaryTable?: string;
  selectedRow: Record<string, any> | null;
  totalCount: number;
  page: number;
  pageSize: number;
  /** 현재 검색 파라미터 — Search 컴포넌트가 onSearch 로 업데이트하면 Table 로 전달 */
  searchParams: Record<string, any>;
  onSearch: (params: Record<string, any>) => void;
  onRowSelect: (row: Record<string, any>) => void;
  onPageChange: (args: { page: number; size: number }) => void;
  onAdd: () => void;
  onEdit: () => void;
  onDelete: () => void;
  formRow?: Record<string, any>;
  onFormRowChange?: (patch: Record<string, any>) => void;
  onFormSubmit?: () => void;
  onFormCancel?: () => void;
}

interface TemplateRendererProps {
  template: Template | any;
  context: TemplateRenderContext;
  view?: ViewKey;
}

// ─────────────────────────────────────────────────────────────────────────────
// TemplateRenderer 본체
// ─────────────────────────────────────────────────────────────────────────────

export function TemplateRenderer({
  template,
  context,
  view = 'list',
}: TemplateRendererProps) {
  const v2Views = useMemo(
    () => ensureV2Views(template?.views ?? template?.VIEWS),
    [template],
  );

  const currentView: ViewV2 | undefined =
    view === 'list'
      ? v2Views.list
      : view === 'create'
        ? v2Views.create
        : v2Views.edit;

  const blocks = currentView?.blocks ?? [];
  // view 별 canvas 우선 — 빌더에서 등록/수정 팝업을 다른 해상도로 편집한 경우,
  // migrate 단계에서 viewCanvases.{view} 가 채워져 있다. 그 값이 최상위 우선.
  const canvas: CanvasV2 =
    (v2Views?.viewCanvases as any)?.[view] ??
    v2Views?.canvas ?? {
      baseWidth: 1920,
      baseHeight: 1080,
      aspectPolicy: 'preserve',
    };

  // 디버그 덤프
  if (typeof window !== 'undefined') {
    const qs = new URLSearchParams(window.location.search);
    const enabled =
      qs.has('debug') ||
      window.localStorage?.getItem('INVYONE_DEBUG') === '1';
    if (enabled && blocks.length > 0) {
      /* eslint-disable no-console */
      console.groupCollapsed(
        '%c[TemplateRenderer]',
        'color:#6c5ce7;font-weight:bold',
        `engine=line view=${view} blocks=${blocks.length}`,
      );
      console.table(
        blocks.map((b) => ({
          id: b.id,
          cid: b.componentId,
          role: b.role,
          policy: b.responsivePolicy,
          bandId: b.bandId ?? '',
          x: b.xPct.toFixed(3),
          y: b.yPct.toFixed(3),
          w: b.wPct.toFixed(3),
          h: b.hPct.toFixed(3),
        })),
      );
      console.groupEnd();
      /* eslint-enable no-console */
    }
  }

  if (!blocks.length) {
    return (
      <div className="flex h-full w-full flex-col items-center justify-center gap-2 py-8 text-center">
        <div className="text-4xl opacity-40">📋</div>
        <div className="text-sm font-medium text-slate-600 dark:text-slate-300">
          {view === 'list' ? '템플릿이 비어있습니다' : '이 뷰가 비어있습니다'}
        </div>
      </div>
    );
  }

  return (
    <LineGridView
      blocks={blocks}
      context={context}
      view={view}
      canvas={canvas}
    />
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// LineGridView — 기본 템플릿 렌더러
//   좌표에서 variable line grid 를 추출해 CSS Grid 로 렌더. role 은 semantic
//   marker 로만 남고 레이아웃 계산에 개입하지 않는다. overlay 는 예외 경로만.
// ─────────────────────────────────────────────────────────────────────────────

function LineGridView({
  blocks,
  context,
  view,
  canvas,
}: {
  blocks: BlockV2[];
  context: TemplateRenderContext;
  view: ViewKey;
  canvas: CanvasV2;
}) {
  const aspectPolicy = canvas.aspectPolicy;

  const layout = useMemo(
    () =>
      computeLayout(
        blocks.map((b) => ({
          id: b.id,
          componentId: b.componentId,
          xPct: b.xPct,
          yPct: b.yPct,
          wPct: b.wPct,
          hPct: b.hPct,
          forceOverlay: b.role === 'overlay',
        })),
        { rowSizing: aspectPolicy === 'preserve' ? 'fr' : 'auto' },
      ),
    [blocks, aspectPolicy],
  );

  const gridTemplateColumns = toTrackTemplate(layout.cols, 'fr');

  const byId = useMemo(() => {
    const m = new Map<string, BlockV2>();
    for (const b of blocks) m.set(b.id, b);
    return m;
  }, [blocks]);

  // ── row type 분류 + fit-to-height allocator (Step 2.7) ──────────────────
  const plans = useMemo(
    () =>
      classifyRows(
        layout,
        blocks.map((b) => ({
          id: b.id,
          hPct: b.hPct,
          role: b.role,
          componentId: b.componentId,
        })),
        canvas.baseHeight,
      ),
    [layout, blocks, canvas.baseHeight],
  );

  // wrapper 실제 세로 크기 측정 — ResizeObserver 로 반응형 추적
  const wrapperRef = useRef<HTMLDivElement>(null);
  const [containerSize, setContainerSize] = useState<{
    width: number;
    height: number;
  } | null>(null);
  useLayoutEffect(() => {
    const el = wrapperRef.current;
    if (!el) return;
    const update = () => {
      const width = el.clientWidth;
      const height = el.clientHeight;
      if (width > 0 && height > 0) setContainerSize({ width, height });
    };
    update();
    const ro = new ResizeObserver(update);
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  // finalPx: containerH 측정 전이면 preferred 그대로(첫 프레임 fallback).
  //          측정된 뒤엔 available 에 정확히 맞도록 재분배.
  const finalPx = useMemo(() => {
    if (containerSize == null) return plans.map((p) => p.preferredPx);
    return allocateRowHeightsPx(plans, containerSize.height);
  }, [plans, containerSize]);

  const gridTemplateRows =
    aspectPolicy === 'free'
      ? layout.rows.map(() => 'auto').join(' ')
      : finalPx.map((px) => `${px}px`).join(' ');

  const runtimeSizeById = useMemo(() => {
    const m = new Map<string, { width: number; height: number }>();
    const containerW = containerSize?.width ?? 0;
    if (containerW <= 0) return m;

    const colWidthsPx = layout.cols.map((c) => c.weight * containerW);

    for (const bl of layout.blocks) {
      if (bl.mode !== 'grid') continue;
      const block = byId.get(bl.blockId);
      const colStart = Math.max(1, bl.colStart ?? 1) - 1;
      const colEnd = Math.max(colStart, (bl.colEnd ?? 1) - 1);
      const rowStart = Math.max(1, bl.rowStart ?? 1) - 1;
      const rowEnd = Math.max(rowStart, (bl.rowEnd ?? 1) - 1);

      let width = 0;
      for (let i = colStart; i < colEnd; i++) width += colWidthsPx[i] ?? 0;

      let height = 0;
      if (aspectPolicy === 'free') {
        height = block ? block.hPct * canvas.baseHeight : 0;
      } else {
        for (let i = rowStart; i < rowEnd; i++) height += finalPx[i] ?? 0;
      }

      m.set(bl.blockId, { width, height });
    }
    return m;
  }, [layout, containerSize, finalPx, byId, aspectPolicy, canvas.baseHeight]);

  // 각 블록 cell 의 final 높이가 preferred 대비 많이 줄어든 경우 compact 클래스.
  //   기준: finalH < preferredH * 0.9 (10% 이상 축소)
  const blockCompact = useMemo(() => {
    const m = new Map<string, boolean>();
    for (const bl of layout.blocks) {
      if (bl.mode !== 'grid') continue;
      const b = byId.get(bl.blockId);
      if (!b) continue;
      const rs = (bl.rowStart ?? 1) - 1;
      const re = (bl.rowEnd ?? 1) - 1;
      if (re <= rs) continue;
      let finalH = 0;
      for (let i = rs; i < re; i++) finalH += finalPx[i] ?? 0;
      const prefH = b.hPct * canvas.baseHeight;
      if (prefH > 0 && finalH < prefH * 0.9) m.set(bl.blockId, true);
    }
    return m;
  }, [layout, byId, finalPx, canvas.baseHeight]);

  // 디버그 — row type / preferred / final / container / deficit·surplus
  useEffect(() => {
    if (typeof window === 'undefined' || blocks.length === 0) return;
    try {
      const qs = new URLSearchParams(window.location.search);
      const enabled =
        qs.has('debug') ||
        window.localStorage?.getItem('INVYONE_DEBUG') === '1';
      if (!enabled) return;
      const totalPref = plans.reduce((s, p) => s + p.preferredPx, 0);
      const totalFinal = finalPx.reduce((s, v) => s + v, 0);
      const ch = containerSize?.height ?? 0;
      const delta = ch > 0 ? ch - totalPref : 0;
      /* eslint-disable no-console */
      console.log(
        '%c[TemplateRenderer/line rows]',
        'color:#00cec9;font-weight:bold',
        {
          containerHeight: ch,
          totalPreferred: Math.round(totalPref),
          totalFinal: Math.round(totalFinal),
          deficit: delta < 0 ? Math.round(-delta) : 0,
          surplus: delta > 0 ? Math.round(delta) : 0,
          template: gridTemplateRows,
          xLines: layout.xLines.map((v) => +v.toFixed(4)),
          yLines: layout.yLines.map((v) => +v.toFixed(4)),
        },
      );
      console.table(
        plans.map((p, i) => ({
          i,
          type: p.type,
          preferredPx: p.preferredPx,
          finalPx: Math.round(finalPx[i] ?? 0),
          floorPx: p.floorPx,
          occupiedBlockCount: p.occupiedBlockCount,
        })),
      );
      /* eslint-enable no-console */
    } catch {
      // ignore
    }
  }, [plans, finalPx, containerSize, gridTemplateRows, blocks.length]);

  // 디버그 덤프 — 엔진/블록수/grid/overlay/cols/rows/tolerance + 블록별 box
  if (typeof window !== 'undefined' && blocks.length > 0) {
    try {
      const qs = new URLSearchParams(window.location.search);
      const enabled =
        qs.has('debug') ||
        window.localStorage?.getItem('INVYONE_DEBUG') === '1';
      if (enabled) {
        const gridCount = layout.blocks.filter((b) => b.mode === 'grid').length;
        const overlayCount = layout.blocks.length - gridCount;
        /* eslint-disable no-console */
        console.log(
          '%c[TemplateRenderer/line]',
          'color:#00cec9;font-weight:bold',
          `engine=line view=${view} blocks=${blocks.length} grid=${gridCount} overlay=${overlayCount} cols=${layout.cols.length} rows=${layout.rows.length} tolX=${layout.xToleranceUsed.toFixed(4)} tolY=${layout.yToleranceUsed.toFixed(4)} canvas=${canvas.baseWidth}x${canvas.baseHeight}`,
        );
        console.table(
          layout.cols.map((c) => ({
            col: c.index + 1,
            start: +c.startPct.toFixed(4),
            end: +c.endPct.toFixed(4),
            weight: +c.weight.toFixed(4),
          })),
        );
        console.table(
          layout.rows.map((r) => ({
            row: r.index + 1,
            start: +r.startPct.toFixed(4),
            end: +r.endPct.toFixed(4),
            weight: +r.weight.toFixed(4),
          })),
        );
        console.table(
          layout.blocks.map((bl) => {
            const b = byId.get(bl.blockId);
            return {
              id: bl.blockId,
              cid: b?.componentId ?? '',
              role: b?.role ?? '',
              policy: b?.responsivePolicy ?? '',
              xPct: b ? +b.xPct.toFixed(4) : '',
              yPct: b ? +b.yPct.toFixed(4) : '',
              wPct: b ? +b.wPct.toFixed(3) : '',
              hPct: b ? +b.hPct.toFixed(3) : '',
              px_w: b ? Math.round(b.wPct * canvas.baseWidth) : '',
              px_h: b ? Math.round(b.hPct * canvas.baseHeight) : '',
              mode: bl.mode,
              span:
                bl.mode === 'grid'
                  ? `c${bl.colStart}-${bl.colEnd} r${bl.rowStart}-${bl.rowEnd}`
                  : '',
              overlay: bl.overlayReason ?? '',
              snap: bl.snapError !== undefined ? +bl.snapError.toFixed(4) : '',
            };
          }),
        );
        /* eslint-enable no-console */
      }
    } catch {
      // ignore
    }
  }

  return (
    <div className="itpl-wrapper itpl-line" ref={wrapperRef}>
      <style>{LINE_CSS}</style>
      <div
        className="itpl-grid"
        style={{ gridTemplateColumns, gridTemplateRows }}
      >
        {layout.blocks.map((bl) => {
          const block = byId.get(bl.blockId);
          if (!block) return null;
          const compact = blockCompact.get(block.id) ? ' compact' : '';
          if (bl.mode === 'grid') {
            return (
              <div
                key={block.id}
                className={`itpl-slot role-${block.role} policy-${block.responsivePolicy}${compact}`}
                data-comp={block.componentId}
                data-col={`${bl.colStart}/${bl.colEnd}`}
                data-row={`${bl.rowStart}/${bl.rowEnd}`}
                style={{
                  gridColumn: `${bl.colStart} / ${bl.colEnd}`,
                  gridRow: `${bl.rowStart} / ${bl.rowEnd}`,
                }}
              >
                <BlockRenderer
                  block={block}
                  context={context}
                  view={view}
                  canvas={canvas}
                  runtimeSize={runtimeSizeById.get(block.id)}
                />
              </div>
            );
          }
          return (
            <div
              key={block.id}
              className={`itpl-overlay role-${block.role} policy-${block.responsivePolicy}${compact}`}
              data-comp={block.componentId}
              data-overlay-reason={bl.overlayReason}
              style={{
                left: `${(bl.leftPct ?? 0) * 100}%`,
                top: `${(bl.topPct ?? 0) * 100}%`,
                width: `${(bl.wPct ?? 0) * 100}%`,
                height: `${(bl.hPct ?? 0) * 100}%`,
              }}
            >
              <BlockRenderer
                block={block}
                context={context}
                view={view}
                canvas={canvas}
              />
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// line 전용 CSS — responsivePolicy / role 별 cell 스타일
//
//   cell (itpl-slot) 은 grid track 이 이미 크기를 결정한다. 내부 컴포넌트가
//   그 크기를 따라가도록 flex-column 1개 자식 레이아웃을 기본값으로 둔다.
//   policy 와 role 에 따라 overflow / content-size 를 재정의한다.
//
//   매트릭스 요약:
//     default (main/companion + fixed)  → cell 채움, overflow visible
//     policy-scroll                     → cell 채움, overflow hidden (내부 auto)
//     policy-reflow                     → cell 채움, overflow hidden
//     policy-wrap                       → flex row wrap, content-size 자식
//     role-action                       → flex row, content-size 자식 (버튼 잘림 방지)
//     overlay                           → absolute, visible 기본
// ─────────────────────────────────────────────────────────────────────────────

const LINE_CSS = `
  .itpl-wrapper.itpl-line {
    position: relative;
    width: 100%;
    height: 100%;
    container-type: inline-size;
    container-name: tpl;
    box-sizing: border-box;
    overflow: hidden;
  }
  .itpl-line .itpl-grid {
    position: relative;
    display: grid;
    width: 100%;
    height: 100%;
    box-sizing: border-box;
  }

  /* 기본 slot — cell 을 채우는 flex column */
  .itpl-line .itpl-slot {
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    min-width: 0;
    min-height: 0;
    overflow: visible;
  }
  .itpl-line .itpl-slot > * {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    width: 100%;
    height: 100%;
  }

  /* scroll / reflow: cell 내부에서 overflow 처리 — 내부 컴포넌트의 auto 스크롤
     을 막지 않도록 cell 에 hidden 만 걸어 child min-size 0 을 보장한다 */
  .itpl-line .itpl-slot.policy-scroll,
  .itpl-line .itpl-slot.policy-reflow {
    overflow: hidden;
  }

  /* wrap: 행 방향 flex-wrap, 자식은 content-size 유지 */
  .itpl-line .itpl-slot.policy-wrap {
    flex-direction: row;
    flex-wrap: wrap;
    align-content: flex-start;
    align-items: center;
    overflow: visible;
  }
  .itpl-line .itpl-slot.policy-wrap > * {
    flex: 0 0 auto;
    width: auto;
    height: auto;
  }

  /* action: 버튼/입력 등이 cell 에 맞춰 늘어나지 않고 content-size 유지.
     단 세로로는 row 경계를 넘지 못하게 clip — table 위 침범 방지. 가로는
     CSS 규약상 x/y 중 하나만 visible 로 두면 다른 축이 auto 가 되므로 양쪽
     hidden 으로 간다. cell 폭이 충분하도록 저작자가 wPct 를 부여한다는 전제. */
  .itpl-line .itpl-slot.role-action {
    flex-direction: row;
    align-items: center;
    overflow: hidden;
  }
  .itpl-line .itpl-slot.role-action > * {
    flex: 0 0 auto;
    width: auto;
    height: auto;
  }

  /* button / button-bar / pagination — 컴포넌트가 inline style 로 주입하는
     design px (예: 120x40) 를 존중한다. wrapper 는 자식의 폭/높이를 강제하지
     않고 좌측 정렬로 감싸기만 한다. 기본 .itpl-slot > * 의 flex:1 1 auto 와
     width/height:100% 를 여기서 완화. !important 는 사용하지 않는다 — 그래야
     ButtonComponent 의 inline 스타일(width:120px 등)이 최종 우승한다. */
  .itpl-line .itpl-slot[data-comp="button"],
  .itpl-line .itpl-slot[data-comp="button-bar"],
  .itpl-line .itpl-slot[data-comp="pagination"] {
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
    overflow: hidden;
  }
  .itpl-line .itpl-slot[data-comp="button"] > *,
  .itpl-line .itpl-slot[data-comp="button-bar"] > *,
  .itpl-line .itpl-slot[data-comp="pagination"] > * {
    flex: 0 0 auto;
    min-width: 0;
    min-height: 0;
    align-self: center;
  }

  /* search — 가로는 cell 폭을 채우되, 세로는 content-size 유지.
     row 가 조금 커져도 검색줄 전체가 100% 높이로 늘어나지 않게 한다. */
  .itpl-line .itpl-slot[data-comp="search"] {
    justify-content: center;
    overflow: hidden;
  }
  .itpl-line .itpl-slot[data-comp="search"] > * {
    flex: 0 0 auto;
    width: 100%;
    height: auto;
    min-width: 0;
    min-height: 0;
    align-self: center;
  }

  /* compact: cell final 높이가 preferred 대비 10% 이상 축소된 경우 부여.
     폰트/padding 을 소폭 줄여 잘림을 완화. outer scroll 은 만들지 않는다. */
  .itpl-line .itpl-slot.compact,
  .itpl-line .itpl-overlay.compact {
    font-size: 0.92em;
    line-height: 1.15;
  }
  .itpl-line .itpl-slot.compact > * {
    gap: 4px;
  }

  /* overlay: absolute, 기본 visible. policy-scroll 인 overlay 는 내부 hidden. */
  .itpl-line .itpl-overlay {
    position: absolute;
    z-index: 2;
    box-sizing: border-box;
    overflow: visible;
  }
  .itpl-line .itpl-overlay.policy-scroll,
  .itpl-line .itpl-overlay.policy-reflow {
    overflow: hidden;
  }
  .itpl-line .itpl-overlay > * {
    width: 100%;
    height: 100%;
    min-width: 0;
    min-height: 0;
  }
`;

