/**
 * 반응 정책 엔진
 * ============================================================================
 *
 * INVYONE 템플릿은 FHD 기준으로 자유배치되지만, 실제 표시 영역(대시보드 카드
 * 크기)은 1/1, 1/2, 1/4 로 줄어들 수 있다. 이때 전체를 일괄 비율 축소하는
 * 대신, 컴포넌트 성격별로 다르게 반응해야 한다.
 *
 * 정책 정의: notes/gbpark/2026-04-19-template-responsive-policy.md
 * 타입 정의: types/invyone-component.ts (ResponsiveConfig, ResponsiveMode)
 *
 * 핵심:
 *   1. 컴포넌트는 4가지 모드로 분류: fixed / scroll / reflow / wrap
 *   2. 인접한 동종 단일 컴포넌트는 런타임에 가상 그룹으로 묶임
 *      - 버튼 여러 개 연속 → button-group (wrap)
 *      - stats 여러 개 연속 → stats-group (reflow)
 *   3. 각 정책은 CSS 인라인 스타일과 래퍼 className 을 반환
 * ============================================================================
 */

import type { CSSProperties } from 'react';
import type {
  ResponsiveAnchor,
  ResponsiveConfig,
  ResponsiveMode,
} from '@/types/invyone-component';

// ─────────────────────────────────────────────────────────────────────────────
// 1. componentId → 기본 반응 모드 매핑
// ─────────────────────────────────────────────────────────────────────────────

/**
 * 단일 컴포넌트의 기본 반응 모드.
 * TemplateRenderer 의 LEGACY_TO_UNIFIED 결과값 기준(통합된 핵심 타입).
 */
const DEFAULT_MODE_BY_ID: Record<string, ResponsiveMode> = {
  // 데이터 영역 — 스크롤
  table: 'scroll',
  search: 'scroll',
  container: 'scroll',
  'split-panel': 'scroll',
  accordion: 'scroll',
  tabs: 'scroll',

  // 카드 그룹 — 재배치
  stats: 'reflow',
  form: 'reflow',

  // 버튼 그룹 — 줄바꿈
  'button-bar': 'wrap',

  // 단일 — 고정
  button: 'fixed',
  input: 'fixed',
  title: 'fixed',
  divider: 'fixed',
  pagination: 'fixed',
};

/** 여러 개 연속 배치 시 그룹 모드로 승격되는 단일 타입. */
const GROUP_PROMOTION: Record<string, ResponsiveMode> = {
  button: 'wrap', // 버튼 두 개 이상 → 버튼 그룹 wrap
  input: 'wrap',
  stats: 'reflow',
  title: 'wrap',
};

/** 카드 폭 전체를 단독 점유하는 풀 타입 (같은 row 허용 안 됨). */
export const FULL_WIDTH_IDS: ReadonlySet<string> = new Set([
  'table',
  'container',
  'search',
  'split-panel',
  'accordion',
  'tabs',
]);

export function getDefaultResponsiveMode(
  componentId: string,
  groupSize = 1,
): ResponsiveMode {
  if (groupSize > 1 && GROUP_PROMOTION[componentId]) {
    return GROUP_PROMOTION[componentId];
  }
  return DEFAULT_MODE_BY_ID[componentId] ?? 'fixed';
}

export function getDefaultMinItemWidth(componentId: string): number {
  switch (componentId) {
    case 'stats':
      return 180;
    case 'form':
      return 240;
    case 'button':
    case 'button-bar':
      return 96;
    case 'input':
      return 160;
    default:
      return 160;
  }
}

// ─────────────────────────────────────────────────────────────────────────────
// 2. anchor 자동 결정 (fixed 모드)
// ─────────────────────────────────────────────────────────────────────────────

/**
 * 디자인 시점 좌표를 기준으로 축소 시 붙일 기준점을 결정.
 * 캔버스를 9등분(좌/중/우 × 상/중/하) 한다.
 */
export function resolveAnchor(
  pos: { left: number; top: number; width: number; height: number },
  canvasWidth: number,
  canvasHeight: number,
): ResponsiveAnchor {
  const centerX = pos.left + pos.width / 2;
  const centerY = pos.top + pos.height / 2;
  const isRight = centerX > canvasWidth * 0.6;
  const isLeft = centerX < canvasWidth * 0.4;
  const isBottom = centerY > canvasHeight * 0.6;

  if (isRight) return isBottom ? 'bottom-right' : 'top-right';
  if (isLeft) return isBottom ? 'bottom-left' : 'top-left';
  return 'center';
}

// ─────────────────────────────────────────────────────────────────────────────
// 3. 모드별 CSS 생성
// ─────────────────────────────────────────────────────────────────────────────

export interface ResolvedPolicy {
  /** 래퍼 요소에 적용할 인라인 스타일 */
  wrapperStyle: CSSProperties;
  /** 래퍼 요소에 추가할 className */
  wrapperClassName: string;
  /** 자식(실제 컴포넌트)에 적용할 스타일 */
  innerStyle: CSSProperties;
  /** 자식에 적용할 className */
  innerClassName: string;
  /** 실제 적용된 모드 (디버그/QA 용) */
  appliedMode: ResponsiveMode;
}

interface ResolveContext {
  componentId: string;
  pos: { left: number; top: number; width: number; height: number };
  canvasWidth: number;
  canvasHeight: number;
  /** 인접 동종이 묶인 그룹 크기 (단독 = 1) */
  groupSize?: number;
  /** 명시적 ResponsiveConfig (미지정 시 기본값 사용) */
  config?: ResponsiveConfig;
}

export function resolveResponsivePolicy(ctx: ResolveContext): ResolvedPolicy {
  const groupSize = ctx.groupSize ?? 1;
  const mode =
    ctx.config?.mode ?? getDefaultResponsiveMode(ctx.componentId, groupSize);

  switch (mode) {
    case 'scroll':
      return resolveScroll(ctx);
    case 'reflow':
      return resolveReflow(ctx);
    case 'wrap':
      return resolveWrap(ctx);
    case 'fixed':
    default:
      return resolveFixed(ctx);
  }
}

function resolveScroll(ctx: ResolveContext): ResolvedPolicy {
  // 테이블/컨테이너: wrapper 는 카드 폭에 맞춰 100% 로 줄어들고 자기는 clip.
  // 가로 스크롤은 TableComponent(또는 다른 container 컴포넌트)가 **자체**
  // overflow:auto 로 처리하므로, wrapper 는 추가 스크롤을 걸지 않는다. 이중
  // 스크롤바가 뜨거나 테이블이 카드 경계 밖으로 튀어나가는 문제를 방지.
  const innerStyle: CSSProperties = {
    width: '100%',
    height: '100%',
  };
  if (ctx.config?.minWidth) {
    innerStyle.minWidth = `${ctx.config.minWidth}px`;
  }
  return {
    wrapperStyle: {
      overflow: 'hidden',
      minHeight: ctx.config?.minHeight,
    },
    wrapperClassName: 'rp-scroll',
    innerStyle,
    innerClassName: '',
    appliedMode: 'scroll',
  };
}

function resolveReflow(ctx: ResolveContext): ResolvedPolicy {
  const minItemWidth =
    ctx.config?.minItemWidth ?? getDefaultMinItemWidth(ctx.componentId);
  const maxColumns = ctx.config?.maxColumns;
  const gap = ctx.config?.gap ?? 8;

  const gridTemplateColumns = maxColumns
    ? `repeat(auto-fit, minmax(max(${minItemWidth}px, calc((100% - ${
        gap * (maxColumns - 1)
      }px) / ${maxColumns})), 1fr))`
    : `repeat(auto-fit, minmax(${minItemWidth}px, 1fr))`;

  return {
    wrapperStyle: {
      display: 'grid',
      gridTemplateColumns,
      gap: `${gap}px`,
      minHeight: ctx.config?.minHeight,
    },
    wrapperClassName: 'rp-reflow',
    innerStyle: { minWidth: 0 },
    innerClassName: '',
    appliedMode: 'reflow',
  };
}

function resolveWrap(ctx: ResolveContext): ResolvedPolicy {
  const gap = ctx.config?.gap ?? 8;
  return {
    wrapperStyle: {
      display: 'flex',
      flexWrap: 'wrap',
      gap: `${gap}px`,
      alignItems: 'flex-start',
      alignContent: 'flex-start',
    },
    wrapperClassName: 'rp-wrap',
    innerStyle: {
      flex: '0 0 auto',
      minWidth: ctx.config?.minWidth,
      minHeight: ctx.config?.minHeight,
    },
    innerClassName: '',
    appliedMode: 'wrap',
  };
}

function resolveFixed(ctx: ResolveContext): ResolvedPolicy {
  // flex row 컨텍스트 안에서 동작. 원본 크기 유지 + flex-basis 로 자리 배정.
  // anchor 는 row 내부에서 좌/우 정렬 힌트로만 사용 (justify-self 기반).
  const anchor =
    ctx.config?.anchor ??
    resolveAnchor(ctx.pos, ctx.canvasWidth, ctx.canvasHeight);

  const wrapperStyle: CSSProperties = {
    width: `${ctx.pos.width}px`,
    height: `${ctx.pos.height}px`,
    flex: '0 0 auto',
  };

  // 우측 앵커면 flex 에서 오른쪽 정렬되게 힌트 — 실제 정렬은 부모 spacer 로 처리
  if (anchor === 'top-right' || anchor === 'bottom-right') {
    wrapperStyle.marginLeft = 'auto';
  }

  return {
    wrapperStyle,
    wrapperClassName: `rp-fixed rp-anchor-${anchor}`,
    innerStyle: { width: '100%', height: '100%' },
    innerClassName: '',
    appliedMode: 'fixed',
  };
}

// ─────────────────────────────────────────────────────────────────────────────
// 4. 그룹 탐지
// ─────────────────────────────────────────────────────────────────────────────

export interface GroupableBlock {
  id: string;
  componentId: string;
  pos: { left: number; top: number; width: number; height: number };
}

export interface ResponsiveGroup<T extends GroupableBlock = GroupableBlock> {
  /** 그룹을 대표하는 컴포넌트 ID ('button', 'stats', 'title' 등) */
  componentId: string;
  /** 런타임 가상 그룹 여부 (ButtonBar 처럼 자체 그룹 컴포넌트면 false) */
  virtual: boolean;
  /** 그룹 멤버 */
  blocks: T[];
  /** 그룹 전체의 bounding box (pos) */
  bbox: { left: number; top: number; width: number; height: number };
}

/**
 * 같은 row 안에서 연속된 동일 componentId 블록을 가상 그룹으로 묶는다.
 * 단, 블록 사이 수평 간격이 크면 (디자이너가 의도적으로 떨어뜨린 배치)
 * 같은 id 여도 별개 그룹으로 분리한다. 예: "중앙 버튼" + "우측 액션 버튼".
 *
 * 그룹 판정 기준 (AND):
 *   1. 같은 componentId
 *   2. gap <= max(직전블록폭 × 0.5, 32px)  — 시각적으로 인접한 것만 묶음
 *
 * FULL_WIDTH 는 그룹 대상이 아니며, 항상 단독 그룹으로 유지된다.
 */
export function detectResponsiveGroups<T extends GroupableBlock>(
  rowBlocks: T[],
): ResponsiveGroup<T>[] {
  if (rowBlocks.length === 0) return [];
  const groups: ResponsiveGroup<T>[] = [];
  const sorted = [...rowBlocks].sort((a, b) => a.pos.left - b.pos.left);

  let current: T[] = [];
  let currentId: string | null = null;

  const flush = () => {
    if (current.length === 0) return;
    groups.push(makeGroup(current, currentId!));
    current = [];
    currentId = null;
  };

  for (const b of sorted) {
    if (FULL_WIDTH_IDS.has(b.componentId)) {
      flush();
      groups.push(makeGroup([b], b.componentId));
      continue;
    }
    if (currentId === null || b.componentId !== currentId) {
      flush();
      currentId = b.componentId;
      current = [b];
      continue;
    }
    // 같은 componentId: gap 검사
    const prev = current[current.length - 1];
    const gap = b.pos.left - (prev.pos.left + prev.pos.width);
    const adjacencyThreshold = Math.max(prev.pos.width * 0.5, 32);
    if (gap > adjacencyThreshold) {
      // 같은 id 지만 시각적으로 떨어져 있음 → 별개 그룹
      flush();
      currentId = b.componentId;
      current = [b];
    } else {
      current.push(b);
    }
  }
  flush();
  return groups;
}

function makeGroup<T extends GroupableBlock>(
  blocks: T[],
  componentId: string,
): ResponsiveGroup<T> {
  const left = Math.min(...blocks.map((b) => b.pos.left));
  const top = Math.min(...blocks.map((b) => b.pos.top));
  const right = Math.max(...blocks.map((b) => b.pos.left + b.pos.width));
  const bottom = Math.max(...blocks.map((b) => b.pos.top + b.pos.height));
  return {
    componentId,
    virtual: blocks.length > 1,
    blocks,
    bbox: { left, top, width: right - left, height: bottom - top },
  };
}
