/**
 * 전략 조건 DSL → 알기 쉬운 한국어 서술형 설명
 */
import type { LogicNode } from './strategyTypes';
import { CONDITION_LABEL } from './strategyTypes';
import {
  collectEditorBranches,
  normalizeStartCombineOp,
  type EditorConditionState,
} from './strategyConditionSerde';
import {
  compositeDisplayName,
  normalizeCompositeCondition,
} from './compositeIndicators';
import {
  getFieldOpts,
  resolveFieldOptionValue,
  type DefType,
} from './strategyEditorShared';
import { parseThresholdField } from './conditionPeriods';
import { formatStartCandleLabel } from './strategyStartNodes';

export interface StrategyDescriptionInput {
  name?: string;
  description?: string;
  buyEditorState: EditorConditionState;
  sellEditorState: EditorConditionState;
  buyCondition: LogicNode | null;
  sellCondition: LogicNode | null;
  orphanCount?: number;
  def: DefType;
}

export interface StrategyNarrativeSection {
  title: string;
  paragraphs: string[];
  bullets?: string[];
}

export interface StrategyNarrative {
  intro: string[];
  sections: StrategyNarrativeSection[];
  footnotes: string[];
}

const CANDLE_KO: Record<string, string> = {
  '1m': '1분봉',
  '3m': '3분봉',
  '5m': '5분봉',
  '15m': '15분봉',
  '30m': '30분봉',
  '1h': '1시간봉',
  '4h': '4시간봉',
  '1d': '일봉',
};

function candleKo(ct: string): string {
  const key = formatStartCandleLabel(ct);
  return CANDLE_KO[key] ?? `${key} 봉`;
}

function fieldLabel(
  indicatorType: string,
  field: string | undefined,
  def: DefType,
  signalType: 'buy' | 'sell',
  cond?: ReturnType<typeof normalizeCompositeCondition>,
): string {
  const opts = getFieldOpts(indicatorType, signalType, def, cond);
  const resolved = resolveFieldOptionValue(indicatorType, field);
  const hit = opts.find(o => o.value === resolved);
  if (hit && hit.label !== '선택안함') return hit.label;
  const thresh = field ? parseThresholdField(field) : null;
  if (thresh != null) return `임계값 ${thresh}`;
  return field ?? indicatorType;
}

function describeConditionType(
  conditionType: string,
  left: string,
  right: string,
  extras: { compareValue?: number; slopePeriod?: number; holdDays?: number },
): string {
  const cv = extras.compareValue;
  const sp = extras.slopePeriod ?? 3;
  const hd = extras.holdDays ?? 3;

  switch (conditionType) {
    case 'GT': return `${left}이(가) ${right}보다 큰 경우`;
    case 'LT': return `${left}이(가) ${right}보다 작은 경우`;
    case 'GTE': return `${left}이(가) ${right} 이상인 경우`;
    case 'LTE': return `${left}이(가) ${right} 이하인 경우`;
    case 'EQ': return `${left}과(와) ${right}이(가) 같은 경우`;
    case 'NEQ': return `${left}과(와) ${right}이(가) 다른 경우`;
    case 'CROSS_UP': return `${left}이(가) ${right}을(를) 상향 돌파하는 경우`;
    case 'CROSS_DOWN': return `${left}이(가) ${right}을(를) 하향 돌파하는 경우`;
    case 'SLOPE_UP': return `${left}이(가) 최근 ${sp}봉 동안 상승 추세인 경우`;
    case 'SLOPE_DOWN': return `${left}이(가) 최근 ${sp}봉 동안 하락 추세인 경우`;
    case 'DIFF_GT': return `${left}과(와) ${right}의 차이가 ${cv ?? 0}보다 큰 경우`;
    case 'DIFF_LT': return `${left}과(와) ${right}의 차이가 ${cv ?? 0}보다 작은 경우`;
    case 'HOLD_N_DAYS': return `조건이 ${hd}봉 연속 유지되는 경우`;
    case 'ABOVE_CLOUD': return '가격이 일목균형표 구름대 위에 있는 경우';
    case 'BELOW_CLOUD': return '가격이 일목균형표 구름대 아래에 있는 경우';
    case 'IN_CLOUD': return '가격이 일목균형표 구름 안에 있는 경우';
    case 'CLOUD_BREAK_UP': return '가격이 구름대를 상향 돌파하는 경우';
    case 'CLOUD_BREAK_DOWN': return '가격이 구름대를 하향 돌파하는 경우';
    case 'SPAN1_GT_SPAN2': return '선행스팬1이 선행스팬2보다 위에 있는 경우';
    case 'SPAN1_LT_SPAN2': return '선행스팬1이 선행스팬2보다 아래에 있는 경우';
    case 'SPAN1_CROSS_UP_SPAN2': return '선행스팬1이 선행스팬2를 상향 돌파하는 경우';
    case 'SPAN1_CROSS_DOWN_SPAN2': return '선행스팬1이 선행스팬2를 하향 돌파하는 경우';
    case 'LAGGING_GT_PRICE': return '후행스팬이 종가보다 위에 있는 경우';
    case 'LAGGING_LT_PRICE': return '후행스팬이 종가보다 아래에 있는 경우';
    default: {
      const label = CONDITION_LABEL[conditionType] ?? conditionType;
      if (right && right !== '선택안함') return `${left} — ${label} — ${right}`;
      return `${left} — ${label}`;
    }
  }
}

function describeCondition(
  cond: ReturnType<typeof normalizeCompositeCondition>,
  signalType: 'buy' | 'sell',
  def: DefType,
): string {
  const ind = cond.indicatorType;
  const ct = cond.conditionType;

  if (cond.composite && cond.leftPeriod && cond.rightPeriod) {
    const name = compositeDisplayName(ind).split(' + ')[0] ?? ind;
    const short = `${name} ${cond.leftPeriod}기간`;
    const long = `${name} ${cond.rightPeriod}기간`;
    switch (ct) {
      case 'GT': return `${short} 값이 ${long} 값보다 큰 경우`;
      case 'LT': return `${short} 값이 ${long} 값보다 작은 경우`;
      case 'GTE': return `${short} 값이 ${long} 값 이상인 경우`;
      case 'LTE': return `${short} 값이 ${long} 값 이하인 경우`;
      case 'CROSS_UP': return `${short} 값이 ${long} 값을 상향 돌파하는 경우`;
      case 'CROSS_DOWN': return `${short} 값이 ${long} 값을 하향 돌파하는 경우`;
      default: {
        const label = CONDITION_LABEL[ct] ?? ct;
        return `${short}과(와) ${long}을(를) 비교할 때 「${label}」 조건이 성립하는 경우`;
      }
    }
  }

  const left = fieldLabel(ind, cond.leftField, def, signalType, cond);
  const right = fieldLabel(ind, cond.rightField, def, signalType, cond);

  if (['ABOVE_CLOUD', 'BELOW_CLOUD', 'IN_CLOUD', 'CLOUD_BREAK_UP', 'CLOUD_BREAK_DOWN'].includes(ct)) {
    return describeConditionType(ct, left, right, cond);
  }

  if (right && right !== '선택안함') {
    return describeConditionType(ct, left, right, cond);
  }

  const label = CONDITION_LABEL[ct] ?? ct;
  return `${left}에 대해 「${label}」 조건이 성립하는 경우`;
}

function describeNode(
  node: LogicNode,
  signalType: 'buy' | 'sell',
  def: DefType,
  depth = 0,
): string[] {
  if (node.type === 'CONDITION' && node.condition) {
    const c = normalizeCompositeCondition(node.condition);
    const line = describeCondition(c, signalType, def);
    return [depth > 0 ? `• ${line}` : line];
  }

  if (node.type === 'TIMEFRAME') {
    const inner = node.children?.[0];
    const tf = candleKo(node.candleType ?? '1m');
    if (!inner) return [`[${tf}] 연결된 조건이 없습니다.`];
    const innerLines = describeNode(inner, signalType, def, depth + 1);
    return [`[${tf} 기준]`, ...innerLines.map(l => (l.startsWith('•') ? `  ${l}` : `  • ${l}`))];
  }

  if (node.type === 'AND') {
    const children = node.children ?? [];
    if (children.length === 0) return ['(비어 있는 AND 그룹)'];
    const lines: string[] = [];
    if (depth === 0 && children.length > 1) {
      lines.push('다음 조건을 모두 동시에 만족해야 합니다.');
    }
    children.forEach((child, i) => {
      const sub = describeNode(child, signalType, def, depth + 1);
      if (children.length > 1) lines.push(`${i + 1})`);
      lines.push(...sub);
    });
    return lines;
  }

  if (node.type === 'OR') {
    const children = node.children ?? [];
    if (children.length === 0) return ['(비어 있는 OR 그룹)'];
    const lines: string[] = [];
    if (depth === 0 && children.length > 1) {
      lines.push('다음 조건 중 하나라도 만족하면 됩니다.');
    }
    children.forEach((child, i) => {
      const sub = describeNode(child, signalType, def, depth + 1);
      if (children.length > 1) lines.push(`${i + 1})`);
      lines.push(...sub);
    });
    return lines;
  }

  if (node.type === 'NOT') {
    const child = node.children?.[0];
    if (!child) return ['(비어 있는 NOT 그룹)'];
    const sub = describeNode(child, signalType, def, depth + 1);
    return ['다음 조건이 성립하지 않을 때:', ...sub.map(l => `  (부정) ${l.replace(/^•\s*/, '')}`)];
  }

  return [];
}

function describeSignalBranches(
  editorState: EditorConditionState | undefined,
  fallbackRoot: LogicNode | null,
  signalType: 'buy' | 'sell',
  def: DefType,
): { hasContent: boolean; paragraphs: string[]; bullets: string[] } {
  const branches = editorState
    ? collectEditorBranches(editorState)
    : fallbackRoot
      ? [{ candleType: '1m', root: fallbackRoot }]
      : [];

  const active = branches.filter(b => b.root);
  if (active.length === 0) {
    return { hasContent: false, paragraphs: [], bullets: [] };
  }

  const bullets: string[] = [];
  const paragraphs: string[] = [];

  if (active.length === 1) {
    const { candleType, root } = active[0];
    const tf = candleKo(candleType);
    paragraphs.push(`${tf} 차트를 기준으로 아래 조건을 평가합니다.`);
    bullets.push(...describeNode(root!, signalType, def));
    return { hasContent: true, paragraphs, bullets };
  }

  const combineOp = normalizeStartCombineOp(editorState?.startCombineOp);
  paragraphs.push(
    combineOp === 'AND'
      ? '여러 시간봉에 걸친 조건을 모두 충족해야 합니다.'
      : '여러 시간봉 중 하나의 조건만 충족해도 됩니다.',
  );

  active.forEach((branch, idx) => {
    const tf = candleKo(branch.candleType);
    bullets.push(`[시간봉 ${idx + 1}: ${tf}]`);
    if (branch.root) {
      bullets.push(...describeNode(branch.root, signalType, def, 1).map(l =>
        l.startsWith('•') ? `  ${l}` : `  • ${l}`,
      ));
    }
  });

  return { hasContent: true, paragraphs, bullets };
}

function hasSignalContent(
  editorState: EditorConditionState,
  fallback: LogicNode | null,
): boolean {
  if (collectEditorBranches(editorState).some(b => b.root)) return true;
  return !!fallback;
}

export function buildStrategyNarrative(input: StrategyDescriptionInput): StrategyNarrative {
  const {
    name,
    description,
    buyEditorState,
    sellEditorState,
    buyCondition,
    sellCondition,
    orphanCount = 0,
    def,
  } = input;

  const intro: string[] = [];
  if (name?.trim()) {
    intro.push(`「${name.trim()}」 전략의 매수·매도 조건을 사람이 읽기 쉬운 문장으로 풀어 쓴 설명입니다.`);
  } else {
    intro.push('현재 편집 중인 전략의 매수·매도 조건을 설명합니다.');
  }
  if (description?.trim()) {
    intro.push(`메모: ${description.trim()}`);
  }

  const sections: StrategyNarrativeSection[] = [];
  const footnotes: string[] = [];

  const buy = describeSignalBranches(buyEditorState, buyCondition, 'buy', def);
  if (hasSignalContent(buyEditorState, buyCondition)) {
    sections.push({
      title: '매수 조건 (진입)',
      paragraphs: [
        '실시간 체크 또는 백테스트에서 아래 매수 조건이 충족되면 매수 신호가 발생합니다.',
        ...buy.paragraphs,
      ],
      bullets: buy.bullets.length > 0 ? buy.bullets : undefined,
    });
  } else {
    sections.push({
      title: '매수 조건 (진입)',
      paragraphs: ['매수 조건이 아직 설정되지 않았습니다. 매수 탭에서 지표·논리 블록을 연결해 주세요.'],
    });
  }

  const sell = describeSignalBranches(sellEditorState, sellCondition, 'sell', def);
  if (hasSignalContent(sellEditorState, sellCondition)) {
    sections.push({
      title: '매도 조건 (청산)',
      paragraphs: [
        '포지션을 보유한 상태에서 아래 매도 조건이 충족되면 매도 신호가 발생합니다. (시그널 모드에 따라 포지션 없이도 표시될 수 있습니다.)',
        ...sell.paragraphs,
      ],
      bullets: sell.bullets.length > 0 ? sell.bullets : undefined,
    });
  } else {
    sections.push({
      title: '매도 조건 (청산)',
      paragraphs: ['매도 조건이 아직 설정되지 않았습니다. 매도 탭에서 조건을 구성해 주세요.'],
    });
  }

  if (orphanCount > 0) {
    footnotes.push(
      `캔버스에 연결되지 않은 요소 ${orphanCount}개는 실제 매매 판정에 포함되지 않습니다.`,
    );
  }
  footnotes.push(
    '각 START 노드에 지정한 시간봉이 실시간 전략 체크·백테스트의 평가 주기가 됩니다.',
  );
  footnotes.push(
    '지표 기간·임계값은 전략 빌더 우측 「전략 조건 전용 설정」에 표시된 값을 기준으로 합니다.',
  );

  return { intro, sections, footnotes };
}
