// ============================================================================
// INVYONE 컴포넌트 규격 v1.0 — TypeScript 타입 정의
// ============================================================================
//
// 설계 원칙:
//   1. 하나의 FieldConfig가 모든 곳(테이블/폼/검색)에서 쓰인다
//   2. 컴포넌트는 DataPort로 통신한다
//   3. 설정은 BaseConfig → TypeConfig → InstanceConfig 3단계
//
// 기존 vex 타입(types/component.ts, types/screen.ts)과 의존성 없음 — 완전 독립
// ============================================================================

// ─────────────────────────────────────────────────────────────────────────────
// 1. FieldConfig — 유일한 필드 규격
// ─────────────────────────────────────────────────────────────────────────────

/**
 * 필드 렌더링 방식을 결정하는 타입.
 *
 * - 테이블에서는 셀 포맷, 폼에서는 입력 위젯, 검색에서는 필터 UI로 매핑된다.
 * - 새 타입 추가 시 이 union에 리터럴만 추가하면 된다.
 */
export type FieldType =
  | 'text'       // 일반 문자열
  | 'number'     // 숫자 (포맷팅)
  | 'date'       // 날짜
  | 'datetime'   // 날짜+시간
  | 'time'       // 시간만
  | 'daterange'  // 기간 (시작 ~ 끝)
  | 'select'     // 드롭다운 (options 배열)
  | 'entity'     // 참조 테이블 code-name 선택
  | 'checkbox'   // 체크박스
  | 'textarea'   // 장문
  | 'file'       // 파일 첨부
  | 'code';      // 자동채번 (readonly)

/**
 * entity 타입 필드의 참조 정보.
 * DB FK 를 직접 걸 수 없는 경우 화면 설정에서 대상 테이블·저장 컬럼·표시 컬럼을 지정한다.
 */
export interface FieldRef {
  /** 참조 대상 테이블 */
  table: string;
  /** 값으로 사용할 컬럼 (PK 등) */
  valueColumn: string;
  /** 화면에 표시할 컬럼 */
  displayColumn: string;
  /** 검색 가능한 표시 컬럼 목록 (명시 검색 UI가 사용할 수 있음) */
  searchColumns?: string[];
  /**
   * 선택 시 다른 필드 자동 채움 매핑.
   * key = 참조 테이블의 컬럼명, value = 폼의 target 필드 키.
   * 예: `{ "customer_name": "buyer_name", "phone": "buyer_phone" }`
   *   → entity 선택 결과 row 의 customer_name → 폼의 buyer_name 필드에 set.
   */
  autoFillMap?: Record<string, string>;
  /**
   * 옵션 결과 사전 필터. 예: `{ "is_active": "Y" }` → 활성 거래처만 노출.
   */
  filter?: Record<string, any>;
  /**
   * 명시 검색 UI 안에 표시할 컬럼 목록. 미지정 시 displayColumn 하나만.
   * 예: `["customer_code", "customer_name", "ceo_name", "phone"]`
   */
  modalColumns?: string[];
}

/**
 * select 타입의 선택지.
 * - string이면 value=label로 해석 (단순 목록)
 * - { value, label } 객체면 value를 저장, label을 표시 (코드값/표시값 분리)
 */
export type FieldOption = string | { value: string; label: string };

/**
 * 모든 컴포넌트가 공유하는 유일한 필드 정의.
 *
 * 테이블 컬럼, 폼 입력, 검색 조건 전부 이 하나의 규격으로 표현한다.
 * 렌더러가 `type`을 보고 적절한 UI를 그린다.
 *
 * vex의 ColumnConfig(354줄) + FilterConfig + FormField → 이 ~30줄로 통합.
 */
export interface FieldConfigCore {
  /** DB 컬럼명 (필드의 유일 키) */
  column: string;
  /** 화면에 표시되는 라벨 */
  label: string;
  /** 렌더링 방식을 결정하는 필드 타입 */
  type: FieldType;
  /** 화면에 보이는지 여부 */
  visible: boolean;
  /** 표시 순서 (작을수록 먼저) */
  order: number;
  /** 필수 입력 여부 */
  required: boolean;
  /** 편집 가능 여부 */
  editable: boolean;
}

/**
 * 공통 표시/입력 속성.
 *
 * v0.1 기준:
 * - `width`, `align` 은 table 쪽 표시 힌트
 * - `defaultValue`, `placeholder` 는 form/search 입력 힌트
 * - 컴포넌트 전용 레이아웃/동작 옵션은 FieldConfig 가 아니라 각 config 로 분리
 */
export interface FieldConfigCommonOptions {
  /** 컬럼 너비 (px, 테이블에서 사용) */
  width?: number;
  /** 텍스트 정렬 */
  align?: 'left' | 'center' | 'right';
  /** 기본값 */
  defaultValue?: unknown;
  /** 입력 힌트 텍스트 */
  placeholder?: string;
}

/**
 * 타입별 확장 속성.
 *
 * 원칙:
 * - select 전용은 `options`
 * - entity 전용은 `ref`
 * - number/date/datetime 표시 힌트는 `format`
 * - 계산 규칙은 `computed`
 */
export interface FieldConfigTypeOptions {
  /** select 타입: 선택지 목록 */
  options?: FieldOption[];
  /** entity 타입: 참조 테이블 code-name 정보 */
  ref?: FieldRef;
  /** 포맷 문자열 (number: '#,##0', date: 'YYYY-MM-DD' 등) */
  format?: string;
  /** 자동 계산 수식 (예: 'quantity * unit_price') */
  computed?: string;
}

/**
 * 메타 속성.
 *
 * 화면 공통 렌더링 계약보다는 저장/동작 힌트에 가깝다.
 */
export interface FieldConfigMeta {
  /** PK 여부 */
  pk?: boolean;
  /** 시스템 필드 여부 (company_code 등, 폼에서 숨김) */
  system?: boolean;
  /** 검색 대상 여부 */
  searchable?: boolean;
  /** 정렬 가능 여부 */
  sortable?: boolean;
}

export interface FieldConfig
  extends FieldConfigCore,
    FieldConfigCommonOptions,
    FieldConfigTypeOptions,
    FieldConfigMeta {}

// ─────────────────────────────────────────────────────────────────────────────
// 2. Component — 유일한 컴포넌트 규격
// ─────────────────────────────────────────────────────────────────────────────

/**
 * 지원하는 컴포넌트 종류.
 *
 * 새 컴포넌트 추가 시 이 union에 리터럴을 추가하고,
 * ComponentTypeConfigMap에 대응하는 설정 타입을 등록한다.
 */
export type ComponentType =
  | 'table'        // 데이터 테이블 (목록)
  | 'form'         // 입력 폼
  | 'search'       // 검색 필터
  | 'button'       // 버튼 (단일)
  | 'button-bar'   // 버튼 그룹
  | 'tabs'         // 탭
  | 'split-panel'  // 분할 패널
  | 'title'        // 텍스트/제목
  | 'stats'        // 통계 카드
  | 'divider'      // 구분선
  | 'pagination';  // 페이지네이션

// ─────────────────────────────────────────────────────────────────────────────
// Position — 단일 자유배치 모델은 §7 FreePosition 참고
// ─────────────────────────────────────────────────────────────────────────────

/**
 * 컴포넌트의 데이터 소스 바인딩.
 */
export interface DataSource {
  /** 바인딩 대상 테이블명 */
  table: string;
  /** 읽기/쓰기 모드 */
  mode: 'read' | 'write' | 'readwrite';
}

/**
 * 모든 컴포넌트의 기본 구조.
 *
 * 타입이 무엇이든 이 구조를 따른다.
 * 타입별 고유 설정은 `config` 필드에 ComponentTypeConfig로 들어간다.
 */
export interface Component {
  // ─── 식별 ───

  /** 컴포넌트 고유 ID */
  id: string;
  /** 컴포넌트 종류 */
  type: ComponentType;
  /** 빌더에서 표시되는 이름 */
  label: string;

  // ─── 위치 (빌더가 관리) ───
  //
  // Component 인터페이스는 INVYONE 규격 v1.0 의 초기 드래프트였고, 자유배치
  // 단일 모델 확정 이후 TemplateComponent (§7) 가 실질 대체이다. 이 인터페이스는
  // 아직 참조 코드가 없어 즉시 삭제 가능한 상태이며, position 필드는 FreePosition
  // 단일 모델로 정리되었다. 자세한 내용은 §7 참조.

  /** 자유배치 위치 (px) */
  position?: FreePosition;

  // ─── 데이터 바인딩 ───

  /** 데이터 소스 설정 */
  dataSource?: DataSource;
  /** 이 컴포넌트가 사용하는 필드 목록 (유일한 필드 규격) */
  fields?: FieldConfig[];

  // ─── 데이터 포트 (컴포넌트 간 통신) ───

  /** 받는 데이터 포트 */
  inputs?: DataPort[];
  /** 내보내는 데이터 포트 */
  outputs?: DataPort[];

  // ─── 타입별 설정 ───

  /** 컴포넌트 타입에 따른 고유 설정 */
  config: ComponentTypeConfig;
}

// ─────────────────────────────────────────────────────────────────────────────
// 3. DataPort — 컴포넌트 간 통신 규격
// ─────────────────────────────────────────────────────────────────────────────

/**
 * DataPort가 전달하는 데이터의 형태.
 *
 * | 타입 | 실제 데이터 |
 * |------|------------|
 * | row | Record<string, any> (단일 행) |
 * | rows | Record<string, any>[] (복수 행) |
 * | value | any (단일 값) |
 * | params | Record<string, any> (검색 파라미터) |
 */
export type DataPortType =
  | 'row'       // 단일 행: Record<string, any>
  | 'rows'      // 복수 행: Record<string, any>[]
  | 'value'     // 단일 값: any
  | 'params';   // 검색 파라미터: Record<string, any>

/**
 * v0.1 예약 포트 이름.
 *
 * 아직 `name` 자체를 이 union으로 강제하지는 않는다.
 * 이유:
 * - 기존 코드와 저장 JSON 호환성 유지
 * - 사용자 정의 포트 확장 여지 보존
 *
 * 대신 아래 이름들은 기본 컴포넌트가 우선적으로 사용한다.
 */
export type ReservedDataPortName =
  | 'searchParams'
  | 'refreshTrigger'
  | 'selectedRow'
  | 'selectedRows'
  | 'loadRow'
  | 'formData'
  | 'savedRow'
  | 'clicked';

/**
 * 컴포넌트가 데이터를 주고받는 포트.
 *
 * v0.1 원칙:
 * - 포트는 "데이터 계약 이름 + 타입"만 가진다
 * - 값 변환/매핑/가공은 포트가 아니라 컴포넌트 내부 또는 별도 액션에서 처리한다
 * - 화면 수준 연결은 Connection 이 담당하고, 런타임은 단순 publish/subscribe 브리지다
 */
export interface DataPort {
  /** 포트 이름 (예: 'selectedRow', 'searchParams') */
  name: string;
  /** 데이터 형태 */
  type: DataPortType;
  /** 연결된 컴포넌트 ID (빌더에서 설정) */
  connectedTo?: string;
}

/**
 * 두 컴포넌트 간 DataPort 연결을 나타낸다.
 *
 * v0.1 에서는 단순 연결만 담당한다.
 * 즉 `from.port` 값을 `to.port` 로 그대로 전달하며, 중간 매핑/변환 규칙은 없다.
 */
export interface Connection {
  /** 연결 고유 ID */
  id: string;
  /** 출발점 (데이터를 보내는 쪽) */
  from: {
    /** 출발 컴포넌트 ID */
    componentId: string;
    /** 출발 output 포트 이름 */
    port: string;
  };
  /** 도착점 (데이터를 받는 쪽) */
  to: {
    /** 도착 컴포넌트 ID */
    componentId: string;
    /** 도착 input 포트 이름 */
    port: string;
  };
}

// ─────────────────────────────────────────────────────────────────────────────
// 4. ComponentTypeConfig — 타입별 설정
// ─────────────────────────────────────────────────────────────────────────────

// --- 4.1 table ---

/** 테이블 기본 정렬 설정 */
export interface DefaultSort {
  /** 정렬 대상 컬럼명 */
  column: string;
  /** 정렬 방향 */
  direction: 'asc' | 'desc';
}

/** 테이블 상단 툴바 표시 설정 */
export interface TableToolbar {
  /** 엑셀 내보내기 버튼 표시 */
  showExcel: boolean;
  /** 새로고침 버튼 표시 */
  showRefresh: boolean;
  /** 필터 버튼 표시 */
  showFilter: boolean;
}

/** 테이블 스타일 종류 */
export type TableStyle = 'default' | 'striped' | 'bordered' | 'compact';

/**
 * 테이블(목록) 컴포넌트 설정.
 *
 * 기본 포트:
 * - outputs: selectedRow(row), selectedRows(rows)
 * - inputs: searchParams(params), refreshTrigger(value)
 */
export interface TableConfig {
  /** 페이지당 행 수 (기본: 20) */
  pageSize: number;
  /** 행 선택 모드 */
  selectionMode: 'none' | 'single' | 'multiple';
  /** 체크박스 컬럼 표시 여부 */
  showCheckbox: boolean;
  /** 인라인 편집 활성화 여부 */
  inlineEdit: boolean;
  /** 화면 로드 시 자동으로 데이터 조회 */
  autoLoad: boolean;
  /** 기본 정렬 (미지정 시 서버 기본) */
  defaultSort?: DefaultSort;
  /** 상단 툴바 설정 */
  toolbar: TableToolbar;
  /** 테이블 스타일 */
  style: TableStyle;
}

// --- 4.2 form ---

/** 폼 섹션 구분 (선택) */
export interface FormSection {
  /** 섹션 라벨 */
  label: string;
  /** 이 섹션에 포함되는 필드의 column 목록 (FieldConfig.column 참조) */
  fields: string[];
}

/** 폼 저장 액션 설정 */
export interface FormSaveAction {
  /** 저장 방식 */
  method: 'INSERT' | 'UPDATE' | 'UPSERT';
  /** 저장 성공 후 표시 메시지 */
  successMessage?: string;
  /** 저장 후 목록 자동 새로고침 여부 */
  refreshAfterSave: boolean;
}

/**
 * 입력 폼 컴포넌트 설정.
 *
 * 기본 포트:
 * - outputs: formData(row), savedRow(row)
 * - inputs: loadRow(row)
 */
export interface FormConfig {
  /** 폼 레이아웃 컬럼 수 */
  columns: 1 | 2 | 3;
  /** 섹션 구분 (미지정 시 단일 섹션) */
  sections?: FormSection[];
  /** 저장 액션 설정 */
  saveAction: FormSaveAction;
}

// --- 4.3 search ---

/**
 * 검색 필터 컴포넌트 설정.
 *
 * 기본 포트:
 * - outputs: searchParams(params)
 * - inputs: 없음
 */
export interface SearchConfig {
  /** 날짜 범위 검색 활성화 (date 타입 필드를 자동으로 범위 입력으로) */
  dateRangeEnabled: boolean;
  /** 초기화 버튼 표시 */
  showResetButton: boolean;
  /** 입력 시 자동 검색 (디바운스) */
  autoSearch: boolean;
  /** 검색 필드 배치 방식 */
  layout: 'inline' | 'stacked';
}

// --- 4.4 button / button-bar ---

/**
 * 버튼 액션 종류 (12종).
 *
 * 새 액션 추가 시 이 union에 리터럴만 추가하고 핸들러를 구현하면 된다.
 */
export type ActionType =
  | 'save'       // 저장
  | 'edit'       // 수정 모드 전환
  | 'delete'     // 삭제
  | 'add'        // 신규 추가
  | 'cancel'     // 취소
  | 'close'      // 닫기
  | 'navigate'   // 페이지 이동
  | 'popup'      // 팝업 열기
  | 'search'     // 검색 실행
  | 'reset'      // 초기화
  | 'submit'     // 제출 (폼)
  | 'approval';  // 승인

/** 버튼 스타일 종류 */
export type ButtonVariant = 'primary' | 'default' | 'destructive' | 'outline' | 'ghost';

/** 버튼의 제어 플로우 연결 설정 */
export interface ButtonFlow {
  /** 연결할 플로우 ID */
  flowId: string;
  /** 플로우 실행 타이밍 (액션 전/후) */
  timing: 'before' | 'after';
}

/**
 * 단일 버튼 컴포넌트 설정.
 *
 * 기본 포트:
 * - outputs: clicked(value)
 * - inputs: 없음
 */
export interface ButtonConfig {
  /** 버튼 텍스트 */
  text: string;
  /** 실행할 액션 종류 */
  actionType: ActionType;
  /** 버튼 스타일 */
  variant: ButtonVariant;
  /** 확인 메시지 (있으면 실행 전 확인 팝업 표시) */
  confirm?: string;
  /** 제어 플로우 연결 (선택) */
  flow?: ButtonFlow;
}

/**
 * 버튼 그룹 컴포넌트 설정.
 * 여러 버튼을 한 줄로 묶어 배치한다.
 */
export interface ButtonBarConfig {
  /** 포함된 버튼 목록 */
  buttons: ButtonConfig[];
}

// --- 4.5 tabs ---

/** 개별 탭 항목 */
export interface TabItem {
  /** 탭에 표시되는 라벨 */
  label: string;
  /** 탭 고유 ID */
  id: string;
}

/**
 * 탭 컴포넌트 설정.
 */
export interface TabsConfig {
  /** 탭 목록 */
  tabs: TabItem[];
  /** 기본 선택 탭 ID */
  defaultTab: string;
}

// --- 4.6 기타 (title, stats, divider, pagination, split-panel) ---

/**
 * 제목/텍스트 컴포넌트 설정.
 */
export interface TitleConfig {
  /** 표시 텍스트 */
  text: string;
  /** 글꼴 크기 (CSS 값, 예: '1.2rem') */
  fontSize: string;
  /** 글꼴 두께 (CSS 값, 예: '600') */
  fontWeight: string;
  /** 텍스트 정렬 */
  align: 'left' | 'center' | 'right';
}

/** 통계 항목 하나 */
export interface StatsItem {
  /** 항목 라벨 */
  label: string;
  /** 집계 대상 컬럼명 */
  column: string;
  /** 집계 방식 */
  aggregation: 'count' | 'sum' | 'avg';
}

/**
 * 통계 카드 컴포넌트 설정.
 *
 * 기본 포트:
 * - outputs: 없음
 * - inputs: data(rows)
 */
export interface StatsConfig {
  /** 통계 항목 목록 */
  items: StatsItem[];
}

/**
 * 구분선 컴포넌트 설정.
 */
export interface DividerConfig {
  /** 선 스타일 */
  style: 'solid' | 'dashed' | 'dotted';
}

/**
 * 페이지네이션 컴포넌트 설정.
 *
 * 기본 포트:
 * - outputs: pageChange(params)
 * - inputs: totalCount(value)
 */
export interface PaginationConfig {
  /** 페이지당 행 수 */
  pageSize: number;
  /** 페이지 크기 선택기 표시 여부 */
  showSizeSelector: boolean;
  /** 선택 가능한 페이지 크기 목록 */
  sizeOptions: number[];
}

/**
 * 분할 패널 컴포넌트 설정.
 * 추후 확장 예정 — 현재는 빈 구조로 예약.
 */
export interface SplitPanelConfig {
  /** 분할 방향 */
  direction?: 'horizontal' | 'vertical';
  /** 초기 분할 비율 (0~1, 왼쪽/위쪽 비율) */
  ratio?: number;
}

// --- ComponentTypeConfig 유니온 ---

/**
 * 모든 컴포넌트 타입별 설정의 유니온 타입.
 *
 * Component.config 필드에 사용되며,
 * Component.type에 따라 실제로 들어가는 타입이 결정된다.
 */
export type ComponentTypeConfig =
  | TableConfig
  | FormConfig
  | SearchConfig
  | ButtonConfig
  | ButtonBarConfig
  | TabsConfig
  | TitleConfig
  | StatsConfig
  | DividerConfig
  | PaginationConfig
  | SplitPanelConfig;

/**
 * ComponentType → ComponentTypeConfig 매핑.
 *
 * 타입 안전한 config 접근이 필요할 때 사용한다.
 * 예: ComponentTypeConfigMap['table'] → TableConfig
 */
export interface ComponentTypeConfigMap {
  table: TableConfig;
  form: FormConfig;
  search: SearchConfig;
  button: ButtonConfig;
  'button-bar': ButtonBarConfig;
  tabs: TabsConfig;
  'split-panel': SplitPanelConfig;
  title: TitleConfig;
  stats: StatsConfig;
  divider: DividerConfig;
  pagination: PaginationConfig;
}

/**
 * 타입 안전한 컴포넌트.
 *
 * Component.type과 Component.config의 타입을 연동시킨다.
 * 예: TypedComponent<'table'>이면 config는 반드시 TableConfig.
 */
export interface TypedComponent<T extends ComponentType> extends Omit<Component, 'type' | 'config'> {
  type: T;
  config: ComponentTypeConfigMap[T];
}

// ─────────────────────────────────────────────────────────────────────────────
// 5. Template — 화면 단위
// ─────────────────────────────────────────────────────────────────────────────

/**
 * 뷰 하나의 설정 (목록, 등록 팝업, 수정 팝업).
 *
 * edit 뷰가 create를 상속하면 extends: 'create'로 지정하고,
 * 다른 부분만 오버라이드한다.
 */
export interface ViewConfig {
  /** 이 뷰에 배치된 컴포넌트 목록 */
  components: Component[];
  /** 상속 대상 뷰 (edit가 create를 상속할 때) */
  extends?: 'create';
  /** 팝업 크기 (create/edit 뷰용) */
  size?: {
    /** 팝업 너비 (px) */
    w: number;
    /** 팝업 높이 (px) */
    h: number;
  };
}

/** 템플릿 상태 */
export type TemplateStatus = 'draft' | 'published';

/**
 * 한 화면 = 한 템플릿.
 *
 * 목록 + 등록 팝업 + 수정 팝업이 하나의 Template에 포함된다.
 * Template.fields가 유일한 진실의 원천이며, 모든 뷰가 이를 공유한다.
 */
export interface Template {
  // ─── 식별 ───

  /** 템플릿 고유 ID */
  templateId: string;
  /** 화면 이름 */
  name: string;
  /** 카드 헤더 아이콘 */
  icon?: string;
  /** 카드 헤더 배지 (예: 'ERP · 영업') */
  badge?: string;
  /** 분류 (예: sales, production, purchase) */
  category: string;
  /** 화면 설명 */
  description?: string;
  /** 카드로 배치될 때 기본 사이즈 */
  defaultSize?: { w: number; h: number };

  // ─── 데이터 ───

  /** 메인 바인딩 테이블 */
  primaryTable: string;
  /** 필드 정의 목록 — 모든 뷰가 이 하나를 공유한다 */
  fields: FieldConfig[];

  // ─── 3뷰 (자유배치 단일 모델) ───

  /** 목록 / 등록 / 수정 세 가지 뷰 */
  views: TemplateViews;

  // ─── 연결 ───

  /** 컴포넌트 간 DataPort 연결 목록 */
  connections: Connection[];

  /** 카드 간 통신 포트 정의 (선택) */
  inputs?: DataPortDef[];
  outputs?: DataPortDef[];

  // ─── 메타 ───

  /** 회사 코드 */
  companyCode: string;
  /** 템플릿 버전 */
  version: number;
  /** 상태 (초안 / 게시됨) */
  status: TemplateStatus;
  /** 생성 일시 (ISO 8601) */
  createdAt: string;
  /** 수정 일시 (ISO 8601) */
  updatedAt: string;
}

// ─────────────────────────────────────────────────────────────────────────────
// 6. 카드 엔진 v2 — 자유배치 단일 모델 (2026-04-10 확정)
// ─────────────────────────────────────────────────────────────────────────────
//
// 진실의 원천: notes/gbpark/2026-04-10-card-engine-final-spec.md
//
// 이 섹션의 타입들이 Phase 1 이후 INVYONE 의 유일한 템플릿/대시보드 모델이다.
// ─────────────────────────────────────────────────────────────────────────────

/**
 * 자유배치 위치 — 단일 위치 모델.
 *
 * 빌더 캔버스(Template 안의 컴포넌트) 와 대시보드 캔버스(Card) 가
 * 모두 이 하나의 타입으로 배치된다. 12-grid / 48-col 분기 없음.
 */
export interface FreePosition {
  /** 좌상단 X (px) */
  left: number;
  /** 좌상단 Y (px) */
  top: number;
  /** 너비 (px) */
  width: number;
  /** 높이 (px) */
  height: number;
}

/**
 * 뷰가 다른 뷰를 여는 방식.
 *
 * 빌더가 "등록" 버튼을 감지하면 viewTrigger 를 자동 세팅하고
 * 대응되는 create 뷰 placeholder 를 만든다.
 */
export interface ViewTrigger {
  /** 어느 뷰를 여는가 */
  targetView: 'create' | 'edit' | 'detail';
  /** 뷰 표시 방식 */
  action: 'open-modal' | 'navigate';
}

/**
 * 반응 정책 모드. 카드/대시보드 영역 축소 시 컴포넌트가 어떻게 반응할지 결정.
 * 자세한 정의: notes/gbpark/2026-04-19-template-responsive-policy.md
 *
 * - fixed  : 크기 유지, 위치만 조정 (단일 버튼/입력/타이틀/구분선)
 * - scroll : 내용 크기 유지, 넘치면 가로 스크롤 (테이블/그리드/검색/컨테이너)
 * - reflow : 폭 부족 시 열 수 재배치 (stats 카드 그룹 5→3→2→1열)
 * - wrap   : 요소 크기 유지, 행 단위 줄바꿈 (버튼 그룹/태그 그룹)
 */
export type ResponsiveMode = 'fixed' | 'scroll' | 'reflow' | 'wrap';

/** fixed 모드에서 축소 시 붙일 기준점. */
export type ResponsiveAnchor =
  | 'top-left'
  | 'top-right'
  | 'bottom-left'
  | 'bottom-right'
  | 'center';

/**
 * 컴포넌트의 반응 정책.
 *
 * 명시하지 않으면 componentId 기반 기본값이 런타임에 주입된다.
 * (예: componentId === 'table' → { mode: 'scroll' })
 *
 * 인접한 동종 단일 컴포넌트들이 같은 row 에 있으면 렌더러가 가상 그룹으로 묶어
 * 그룹 정책을 적용한다 — 버튼 여러 개는 wrap, stats 여러 개는 reflow.
 */
export interface ResponsiveConfig {
  /** 반응 모드 (미지정 시 componentId 기반 기본값) */
  mode?: ResponsiveMode;
  /** 최소 폭 (px) — 이 이하로는 줄지 않음 */
  minWidth?: number;
  /** 최소 높이 (px) */
  minHeight?: number;
  /** 컨테이너 overflow 정책 */
  overflowX?: 'hidden' | 'auto' | 'scroll';
  overflowY?: 'hidden' | 'auto' | 'scroll';
  /** fixed 모드: 축소 시 붙일 기준점 (미지정 시 디자인 시점 위치로 자동 결정) */
  anchor?: ResponsiveAnchor;
  /** reflow 모드: grid auto-fit 의 minmax 최소값 (px) */
  minItemWidth?: number;
  /** reflow 모드: 최대 컬럼 수 */
  maxColumns?: number;
  /** reflow/wrap 모드: 아이템 간격 (px) */
  gap?: number;
}

/**
 * Template 안에 배치된 컴포넌트 하나.
 *
 * 카드 내부는 flex-column 자동 레이아웃이므로 px 좌표 대신 `order` 와
 * 선택적 `row` 로만 위치가 결정된다. 카드 폭이 변하면 runtime 이 자동
 * 재배치(줄바꿈/세로 스택)한다. FreePosition 은 오직 대시보드에 카드를
 * 배치하는 상위 수준(Card.position)에서만 사용된다.
 */
export interface TemplateComponent {
  /** 인스턴스 ID */
  id: string;

  /**
   * 컴포넌트 종류 — ComponentRegistry 의 ID 참조.
   * 예: 'v2-table-list', 'v2-button-primary', 'v2-bom-tree'
   */
  componentId: string;

  /** 빌더에서 표시되는 라벨 (선택) */
  label?: string;

  /**
   * 세로 스택 내 순서. 작을수록 위. 0 부터 시작.
   * 같은 `row` 값을 가진 연속 블록끼리는 가로 나열되고, 이때 `order`
   * 는 그 행 안에서 왼쪽에서 오른쪽으로의 순서도 함께 결정한다.
   */
  order: number;

  /**
   * 같은 값을 공유하는 연속된 블록들을 flex-row 로 묶는 키.
   * undefined 면 단독 행(기본). 숫자값 자체는 의미 없고 키 역할만 한다.
   */
  row?: number;

  /**
   * 컴포넌트별 설정 — 모드/옵션/컬럼 정의 등.
   * ComponentRegistry 의 default_config 를 인스턴스마다 오버라이드한다.
   * 로우코드 플랫폼 원칙상 느슨한 Record 형태.
   */
  config: Record<string, any>;

  /** 컨테이너 컴포넌트의 자식 (탭/아코디언 등) */
  children?: TemplateComponent[];

  /** 받는 데이터 포트 */
  inputs?: DataPort[];
  /** 내보내는 데이터 포트 */
  outputs?: DataPort[];

  /** 이 컴포넌트가 다른 뷰를 여는지 여부 (예: 등록 버튼 → create 뷰) */
  viewTrigger?: ViewTrigger;

  /** 반응 정책 (미지정 시 componentId 기반 기본값 주입) */
  responsive?: ResponsiveConfig;

  /** 그룹핑용 — 여러 컴포넌트를 하나의 group 으로 묶을 때 부모 id */
  parentId?: string;

  /**
   * group 전용 — 이 컴포넌트가 type='group' 일 때 자식 id 목록.
   * 빌더의 그룹 박스 UI 용이며, 런타임 렌더러는 parentId 로 부모를 찾는다.
   */
  groupChildren?: string[];
}

/**
 * 한 뷰의 자유배치 캔버스 설정.
 *
 * Template 의 views.list / create / edit 각각이 이 타입이다.
 * 기존 §5 의 ViewConfig 와 이름이 겹치므로 Phase 1 Step 5 에서
 * 기존 ViewConfig 를 이 타입으로 교체한다(그 사이에는 별칭으로 사용).
 */
export interface TemplateViewConfig {
  /** 이 뷰에 배치된 컴포넌트들 (자유배치) */
  components: TemplateComponent[];

  /** 뷰 표시 방식 (list 는 카드 본체, create/edit 는 모달) */
  layout?: 'card' | 'modal';

  /** 모달 사이즈 (layout === 'modal' 일 때) */
  modalSize?: { w: number; h: number };

  /** 뷰 캔버스의 기본 사이즈 (디자인 시의 작업 영역 크기) */
  designSize?: { w: number; h: number };
}

/** Template 의 3뷰 묶음. list 는 필수, create/edit 는 선택(자동 생성 가능). */
export interface TemplateViews {
  /** 목록 뷰 — 카드 본체 (필수) */
  list: TemplateViewConfig;
  /** 등록 뷰 — 모달 (자동 생성 가능) */
  create?: TemplateViewConfig;
  /** 수정 뷰 — 모달 (자동 생성 가능) */
  edit?: TemplateViewConfig;
}

/**
 * Template 단위 데이터 포트 정의 (카드 간 통신용).
 *
 * 개별 TemplateComponent 의 DataPort 와 달리, 이건 Template 전체가
 * 대시보드에서 어떤 입출력을 외부에 노출하는지를 기술한다.
 */
export interface DataPortDef {
  /** 포트 이름 */
  name: string;
  /** 포트 데이터 형태 */
  type: DataPortType;
  /** 설명 (대시보드 연결 UI 에서 표시) */
  description?: string;
}

/**
 * 대시보드에 배치된 카드 한 개 — Template 의 인스턴스.
 *
 * 1 Template : N Card 관계. 한 Template 을 여러 대시보드에 배치할 수 있고,
 * 각 인스턴스마다 위치/크기/설정 오버라이드를 가진다.
 */
export interface Card {
  /** 인스턴스 ID */
  id: string;

  /** 참조하는 Template ID */
  templateId: string;

  /** 대시보드 안에서의 위치 (px 자유배치) */
  position: FreePosition;

  /** 접힘 상태 (mini 모드) */
  collapsed: boolean;

  /** Template 의 config 를 이 인스턴스에서만 덮어쓸 값 */
  configOverride?: Record<string, any>;

  /** 인스턴스별 DataPort 연결 상태 */
  inputs?: DataPort[];
  outputs?: DataPort[];
}

/**
 * 대시보드 레벨 카드 간 연결.
 *
 * 예: 수주 카드의 selectedRow 를 BOM 카드의 masterRow 로 연결.
 */
export interface CardConnection {
  /** 연결 ID */
  id: string;
  /** 출발 카드 + 포트 */
  from: { cardId: string; port: string };
  /** 도착 카드 + 포트 */
  to: { cardId: string; port: string };
}

// ─────────────────────────────────────────────────────────────────────────────
// 7. 템플릿 저장 모델 v2 — % 좌표 + semantic role (2026-04-20)
// ─────────────────────────────────────────────────────────────────────────────
//
// 설계: notes/gbpark/2026-04-20-template-storage-model-v2.md
//
// 목표: 런타임 추측 제거. 디자이너가 의도(role, responsivePolicy)를 명시하고
// 런타임은 그대로 렌더한다. v1 의 FIXED_SMALL_IDS/FULL_WIDTH_IDS 하드코딩,
// centerY overlap 판정, top tolerance clustering, action line clamp 등
// 좌표 기반 추론 전부 폐기 대상.
//
// DB 저장 경로는 기존과 동일 (TEMPLATES.VIEWS jsonb). views 최상위에
// `version: 2` 가 있으면 v2 포맷, 없으면 v1 로 해석해서 런타임에 migrate.
// ─────────────────────────────────────────────────────────────────────────────

/**
 * 블록의 의미상 역할 — 런타임 렌더 전략을 결정한다.
 * 좌표나 크기로 추측하지 않고 디자이너가 명시.
 */
export type BlockRole =
  | 'main'       // 페이지의 주 콘텐츠 (카드/테이블/폼)
  | 'action'     // main band 앞/뒤 별도 액션 라인 (저장/수정 바 등)
  | 'companion'  // main 과 같은 라인에 가로로 공존 (인디케이터, 뱃지 등)
  | 'overlay';   // main 위에 absolute 로 띄우는 floating 요소

/**
 * 카드(컨테이너) 폭이 변할 때 블록이 어떻게 반응할지 — 디자이너가 명시.
 * v1 의 `ResponsiveConfig.mode` 와 동의어지만, v2 에서는 enum 단일 필드.
 */
export type ResponsivePolicy =
  | 'fixed'   // 원본 크기 유지 (단일 버튼/입력/타이틀/구분선)
  | 'scroll'  // 내부 overflow (테이블/컨테이너)
  | 'reflow'  // auto-fit grid (stats 카드 그룹)
  | 'wrap';   // flex-wrap (버튼 그룹)

/**
 * fixed / overlay 모드에서 축소 시 앵커 지점.
 * 9분할 기준 (좌/중/우 × 상/중/하).
 */
export type BlockAnchor =
  | 'top-left'    | 'top-center'    | 'top-right'
  | 'center-left' | 'center'        | 'center-right'
  | 'bottom-left' | 'bottom-center' | 'bottom-right';

/**
 * v2 블록 — 한 뷰 안에 배치된 컴포넌트 인스턴스.
 *
 * 좌표는 정규화된 % 값 (0~1, 기준 캔버스 대비). 디자이너는 px 로 편집하고
 * 저장/로드 경계에서만 정규화/역정규화한다.
 */
export interface BlockV2 {
  /** 인스턴스 ID */
  id: string;
  /** 컴포넌트 종류 — 통합 ID (table, stats, button, …) */
  componentId: string;

  // ─── 좌표 (기준 캔버스 대비 0.0 ~ 1.0) ───
  xPct: number;
  yPct: number;
  wPct: number;
  hPct: number;

  // ─── 디자이너 의도 ───
  /** 의미상 역할 — 런타임 렌더 전략 결정 */
  role: BlockRole;
  /** 반응 정책 */
  responsivePolicy: ResponsivePolicy;
  /** 앵커 (선택 — 미지정 시 fixed 모드 기본값) */
  anchor?: BlockAnchor;
  /** 소속 band ID (선택 — companion/action 이 어느 main band 에 붙는지 명시) */
  bandId?: string;

  /** 컴포넌트 타입별 설정 */
  config: Record<string, any>;
}

/**
 * v2 캔버스 메타 — 정규화 기준 + 축소 정책.
 */
export interface CanvasV2 {
  /** 정규화 기준 폭 (저장 시의 디자이너 캔버스 폭) */
  baseWidth: number;
  /** 정규화 기준 높이 */
  baseHeight: number;
  /**
   * 카드 폭이 canvas 폭보다 좁아질 때 세로 축소 정책.
   * - 'preserve': 세로도 비례 축소 (aspect ratio 유지)
   * - 'free'   : 세로는 content 기반, 가로만 비례 축소
   */
  aspectPolicy: 'preserve' | 'free';
}

/**
 * v2 뷰 — 한 뷰(list/create/edit) 의 블록 모음.
 */
export interface ViewV2 {
  blocks: BlockV2[];
}

/**
 * v2 템플릿 views 최상위 — DB 의 TEMPLATES.VIEWS jsonb 포맷.
 * version 필드로 v1 과 구분한다.
 */
export interface TemplateViewsV2 {
  version: 2;
  /** 기본/폴백 canvas (legacy 공통 screenResolution). view 별 override 없을 때 사용. */
  canvas: CanvasV2;
  list: ViewV2;
  create?: ViewV2;
  edit?: ViewV2;
  /**
   * 뷰별 canvas — 등록/수정 팝업이 목록과 다른 해상도로 편집되는 경우.
   * 빌더의 viewScreenResolutions 를 반영. 컴포넌트 좌표는 해당 뷰의 canvas 기준으로 정규화.
   */
  viewCanvases?: {
    list?: CanvasV2;
    create?: CanvasV2;
    edit?: CanvasV2;
  };
}

// ─────────────────────────────────────────────────────────────────────────────
// 7 (cont.) — 기존 Dashboard 타입 (v1)
// ─────────────────────────────────────────────────────────────────────────────

/**
 * 사이드바 메뉴 항목 = 카드 컬렉션.
 *
 * INVYONE 의 "메뉴 = 대시보드" 원칙에 따라, 사이드바 엔트리와 대시보드가
 * 1:1 대응한다. 별도 대시보드 UI 없음.
 */
export interface Dashboard {
  /** 대시보드 ID */
  id: string;
  /** 메뉴 표시 이름 */
  name: string;
  /** 사이드바 아이콘 */
  icon: string;

  /** 카드 자유배치 목록 */
  cards: Card[];

  /** 카드 간 데이터 연결 */
  connections: CardConnection[];

  // ─── 메타 ───
  companyCode: string;
  /** 사용자별 대시보드일 때 소유자 ID (공유 대시보드는 비움) */
  ownerId?: string;
  /** 공유 대시보드 여부 */
  isShared: boolean;
  createdAt: string;
  updatedAt: string;
}
