import { projectDirectoryPath, projectFilePath } from '@learnitive/project-storage';
import { canonicalProfileForMode, type ProjectProfile } from '@learnitive/project-runtime';
import type { CoreServices } from '@learnitive/context';
import { interactiveContentToMarkdown, normalizeInteractiveContentDocument } from './document';
import { INTERACTIVE_DOC_BLOCK_PROFILE } from './profiles';

const LEGACY_DOCUMENT_PATH = projectFilePath('document.interactive.json');
const LEGACY_BACKUP_DIRECTORY = projectDirectoryPath('.learnitive/legacy');
const LEGACY_BACKUP_PATH = projectFilePath('.learnitive/legacy/document.interactive.json');
const encoder = new TextEncoder();
const decoder = new TextDecoder();

function isLegacyInteractiveDocProfile(profile: ProjectProfile | undefined): boolean {
  return profile?.modeId === 'interactive-doc' || profile?.projectType === 'interactive-doc';
}

async function chooseMarkdownEntry(provider: Awaited<ReturnType<CoreServices['projectStorage']['getProvider']>>): Promise<'main.md' | 'document.md' | 'interactive-document.md'> {
  for (const candidate of ['main.md', 'document.md', 'interactive-document.md'] as const) {
    const existing = await provider.readFile(projectFilePath(candidate)).catch(() => undefined);
    if (!existing) return candidate;
  }
  return 'interactive-document.md';
}

export async function migrateLegacyInteractiveDocProject(
  core: CoreServices,
  wsName: string,
  profile: ProjectProfile | undefined,
): Promise<{ profile: ProjectProfile; entry: string; migrated: boolean }> {
  if (!isLegacyInteractiveDocProfile(profile)) {
    if (!profile) throw new Error('Project profile is unavailable.');
    return { profile, entry: profile.entry || 'main.md', migrated: false };
  }

  const provider = await core.projectStorage.getProvider(wsName);
  const sourceBytes = await provider.readFile(LEGACY_DOCUMENT_PATH);
  if (!sourceBytes) throw new Error('Legacy Interactive Doc source is missing: document.interactive.json.');

  const source = decoder.decode(sourceBytes);
  const document = normalizeInteractiveContentDocument(JSON.parse(source), INTERACTIVE_DOC_BLOCK_PROFILE);
  const markdown = interactiveContentToMarkdown(document);
  const entry = await chooseMarkdownEntry(provider);
  const entryPath = projectFilePath(entry);

  // The legacy source is backed up before any project identity changes. The
  // original root copy is removed only after Markdown + profile persistence
  // succeed, so a failed migration leaves the old project recoverable.
  await provider.createDirectory(LEGACY_BACKUP_DIRECTORY).catch(() => undefined);
  const backup = await provider.readFile(LEGACY_BACKUP_PATH).catch(() => undefined);
  if (!backup) await provider.writeFile(LEGACY_BACKUP_PATH, sourceBytes);
  await provider.writeFile(entryPath, encoder.encode(markdown || '# Document\n'));

  const verified = await provider.readFile(entryPath);
  if (!verified || decoder.decode(verified) !== (markdown || '# Document\n')) {
    throw new Error('Markdown migration verification failed; the legacy document was left intact.');
  }

  const markdownProfile = { ...canonicalProfileForMode('markdown'), entry };
  const persisted = await core.projectStorage.updateProjectProfile(wsName, markdownProfile);
  await provider.deleteFile(LEGACY_DOCUMENT_PATH).catch(() => undefined);
  return { profile: persisted, entry, migrated: true };
}
