import { createRandomId } from "@best-agent/foundation";
import type {
  NodeContract,
  PlanProjection,
  PlanStatus,
  PlanTodo,
  ThreadId,
} from "@best-agent/protocol";
import type { ThreadPlanState, ThreadStore } from "./thread-store.js";

export class LocalPlanStore {
  readonly #threads: ThreadStore;

  constructor(threads: ThreadStore) {
    this.#threads = threads;
  }

  async getProjection(threadId: ThreadId): Promise<PlanProjection | null> {
    const state = await this.#threads.getPlanState(threadId);
    return state?.plan ?? null;
  }

  async saveProjection(plan: PlanProjection): Promise<PlanProjection> {
    const previous = await this.#threads.getPlanState(plan.threadId);
    const saved = await this.#threads.savePlanState({
      nodeContracts: collectNodeContracts(previous),
      plan,
    });
    return saved.plan;
  }

  async updateProjectionTodos(threadId: ThreadId, todos: PlanTodo[]): Promise<PlanProjection> {
    const existing = await this.#requirePlanState(threadId);
    const nextPlan: PlanProjection = {
      ...existing.plan,
      approvedRevision: undefined,
      graphRevision: existing.plan.graphRevision + 1,
      rejectedRevision: undefined,
      todos,
      updatedAt: now(),
    };
    const saved = await this.#threads.savePlanState({
      nodeContracts: collectNodeContracts(existing),
      plan: nextPlan,
    });
    return saved.plan;
  }

  async updateProjectionStatus(threadId: ThreadId, status: PlanStatus): Promise<PlanProjection> {
    const existing = await this.#requirePlanState(threadId);
    const nextPlan: PlanProjection = {
      ...existing.plan,
      approvedRevision:
        status === "approved" ? existing.plan.graphRevision : existing.plan.approvedRevision,
      rejectedRevision:
        status === "rejected" ? existing.plan.graphRevision : existing.plan.rejectedRevision,
      status,
      updatedAt: now(),
    };
    const saved = await this.#threads.savePlanState({
      nodeContracts: collectNodeContracts(existing),
      plan: nextPlan,
    });
    return saved.plan;
  }

  async exportProjection(threadId: ThreadId): Promise<string | null> {
    const state = await this.#threads.getPlanState(threadId);
    if (!state) {
      return null;
    }
    return renderPlanProjection(state.plan);
  }

  async cloneProjection(
    sourceThreadId: ThreadId,
    targetThreadId: ThreadId,
  ): Promise<PlanProjection | null> {
    const existing = await this.#threads.getPlanState(sourceThreadId);
    if (!existing) {
      return null;
    }

    const clonedPlan: PlanProjection = {
      ...existing.plan,
      approvedRevision: undefined,
      graphId: createRandomId("graph"),
      graphRevision: 1,
      rejectedRevision: undefined,
      threadId: targetThreadId,
      updatedAt: now(),
    };
    const saved = await this.#threads.savePlanState({
      nodeContracts: [],
      plan: clonedPlan,
    });
    return saved.plan;
  }

  async #requirePlanState(threadId: ThreadId): Promise<ThreadPlanState> {
    const existing = await this.#threads.getPlanState(threadId);
    if (!existing) {
      throw new Error(`Plan projection for thread ${threadId} not found`);
    }
    return existing;
  }
}

export function renderPlanProjection(plan: PlanProjection): string {
  const lines: string[] = [];
  lines.push(`# ${plan.title}`);
  lines.push("");
  lines.push(`Status: ${plan.status}`);
  lines.push("");
  lines.push("## Summary");
  lines.push(plan.summary || "(empty)");
  lines.push("");
  lines.push("## Todos");
  if (plan.todos.length === 0) {
    lines.push("- (none)");
  } else {
    for (const todo of plan.todos) {
      lines.push(renderPlanTodo(todo));
    }
  }
  lines.push("");
  lines.push("## Risks");
  lines.push(...renderList(plan.risks));
  lines.push("");
  lines.push("## Open Questions");
  lines.push(...renderList(plan.openQuestions));
  lines.push("");
  lines.push("## Acceptance Criteria");
  lines.push(...renderList(plan.acceptanceCriteria));
  lines.push("");
  lines.push("## Affected Paths");
  lines.push(...renderList(plan.affectedPaths));
  lines.push("");
  lines.push("## Evidence");
  lines.push(...renderList(plan.evidenceRefs));
  return lines.join("\n").trim();
}

function collectNodeContracts(state: ThreadPlanState | null): NodeContract[] | undefined {
  if (!state) {
    return undefined;
  }
  const contracts = state.nodes.flatMap((node) => (node.contract ? [node.contract] : []));
  return contracts.length > 0 ? contracts : undefined;
}

function renderList(values: string[]): string[] {
  if (values.length === 0) {
    return ["- (none)"];
  }
  return values.map((value) => `- ${value}`);
}

function renderPlanTodo(todo: PlanTodo): string {
  const checkbox = todo.status === "done" ? "[x]" : "[ ]";
  const suffix = describePlanTodoStatus(todo.status);
  return suffix ? `- ${checkbox} ${todo.content} (${suffix})` : `- ${checkbox} ${todo.content}`;
}

function describePlanTodoStatus(status: PlanTodo["status"]): string | undefined {
  switch (status) {
    case "in_progress":
      return "in progress";
    case "blocked":
      return "blocked";
    case "cancelled":
      return "cancelled";
    default:
      return undefined;
  }
}

function now(): string {
  return new Date().toISOString();
}
