export interface DelegateRun {
  delegateRunId: string;
  parentRunId: string | null;
  description: string;
  depth: number;
  status: 'success' | 'aborted' | 'needs_user_input' | 'running';
  startTs: string;
  endTs: string | null;
  eventCount: number;
  toolCalls: number;
  resultPreview?: string | null;
  totalTokens?: number;
  toolSummary?: Array<{ tool: string; count: number }>;
  filesChanged?: string[];
}

export interface DelegateRunNode extends DelegateRun {
  children: DelegateRunNode[];
}

/** サブタスクジョブに紐づく delegate run のグループ。Task 7 でレンダリングされる。 */
export interface SubtaskDelegateGroup {
  jobId: string;
  issueNumber: number;
  depth: number;
  status: string;
  runs: DelegateRun[];
}

/** GET /:id/delegate-runs のレスポンス型。 */
export interface DelegateRunsResult {
  runs: DelegateRun[];
  subtasks: SubtaskDelegateGroup[];
}

/** delegate run の status に対応する i18n ラベルキーと Tailwind 色クラス。 */
export function delegateStatusBadge(
  status: DelegateRun['status'],
): { labelKey: string; cls: string } {
  switch (status) {
    case 'success': return { labelKey: 'subtasks.delegateStatus.success', cls: 'bg-emerald-100 text-emerald-800' };
    case 'aborted': return { labelKey: 'subtasks.delegateStatus.aborted', cls: 'bg-red-100 text-red-800' };
    case 'needs_user_input': return { labelKey: 'subtasks.delegateStatus.needs_user_input', cls: 'bg-amber-100 text-amber-800' };
    default: return { labelKey: 'subtasks.delegateStatus.running', cls: 'bg-blue-100 text-blue-800' };
  }
}

/** endTs があればそこまで、無ければ now までの経過。clock skew で負にならないようガード。 */
export function formatElapsed(startTs: string, endTs: string | null, now: number): string {
  const end = endTs ? new Date(endTs).getTime() : now;
  const ms = Math.max(0, end - new Date(startTs).getTime());
  if (ms < 1000) return `${ms}ms`;
  if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`;
  return `${Math.floor(ms / 60_000)}m${Math.floor((ms % 60_000) / 1000)}s`;
}

/** トークン数を人間が読みやすい形式にフォーマット。例: 999 → "999 tok", 1000 → "1.0k tok"。 */
export function formatTokens(n: number): string {
  if (n < 1000) return `${n} tok`;
  if (n < 1_000_000) return `${(n / 1000).toFixed(1)}k tok`;
  return `${(n / 1_000_000).toFixed(1)}M tok`;
}

/**
 * Build a tree from a flat list of DelegateRun records.
 * Runs whose parentRunId is null, or whose parent is not in the set,
 * are treated as roots (orphan fallback — no data loss).
 * Sorts roots and children by startTs (ascending), with delegateRunId as tiebreaker.
 */
export function buildDelegateRunTree(runs: DelegateRun[]): DelegateRunNode[] {
  const nodes = new Map<string, DelegateRunNode>();
  for (const r of runs) {
    nodes.set(r.delegateRunId, { ...r, children: [] });
  }
  const roots: DelegateRunNode[] = [];
  for (const node of nodes.values()) {
    const parent = node.parentRunId ? nodes.get(node.parentRunId) : undefined;
    if (parent) {
      parent.children.push(node);
    } else {
      roots.push(node);
    }
  }
  // Sort roots and children by startTs (ascending), tiebreak by delegateRunId
  const sortFn = (a: DelegateRunNode, b: DelegateRunNode) => {
    const cmp = a.startTs.localeCompare(b.startTs);
    return cmp !== 0 ? cmp : a.delegateRunId.localeCompare(b.delegateRunId);
  };
  roots.sort(sortFn);
  for (const node of nodes.values()) {
    node.children.sort(sortFn);
  }
  return roots;
}

/**
 * Determine the currently running tool from a sequence of trace events.
 * Scans events in order: tool_call marks a tool as pending,
 * tool_result for the same tool clears the pending state.
 * Returns the name of the tool still pending (not yet completed),
 * or null if no tool is currently running.
 */
export function currentRunningTool(
  events: Array<{ kind: string; payload: unknown }>
): string | null {
  let pending: string | null = null;
  for (const event of events) {
    if (event.kind === 'tool_call') {
      const payload = event.payload as Record<string, unknown> | null;
      const tool = payload?.tool;
      if (typeof tool === 'string') {
        pending = tool;
      }
    } else if (event.kind === 'tool_result') {
      const payload = event.payload as Record<string, unknown> | null;
      const tool = payload?.tool;
      if (typeof tool === 'string' && tool === pending) {
        pending = null;
      }
    }
  }
  return pending;
}

/**
 * Summarize descendants of a node (children recursively, excluding self).
 * Returns { total, failed, running } counts.
 * - failed = runs with status === 'aborted'
 * - running = runs with status === 'running'
 */
export function summarizeDescendants(node: DelegateRunNode): { total: number; failed: number; running: number } {
  let total = 0;
  let failed = 0;
  let running = 0;

  const traverse = (n: DelegateRunNode) => {
    for (const child of n.children) {
      total++;
      if (child.status === 'aborted') failed++;
      if (child.status === 'running') running++;
      traverse(child);
    }
  };

  traverse(node);
  return { total, failed, running };
}
