import { copyBytesToArrayBuffer, isLearnitiveEditableTextPath } from '@learnitive/project-storage';
import { WsPath } from '@learnitive/ws-path';
import type { FileSystemService } from './file-system-service';
import type { LatexSourceService } from './latex-source-service';
import type { WorkspaceOpsService } from './workspace-ops-service';

export type GeneratedResearchArtifact = {
  path: string;
  kind: 'plot' | 'dataset' | 'table' | 'report' | 'binary' | 'other';
  createdAt: number;
  generatedBy?: {
    command?: string;
    sourcePath?: string;
    runtime?: string;
    inputPaths?: readonly string[];
    runId?: string;
    args?: readonly string[];
    sourceFingerprint?: string;
    inputFingerprints?: Readonly<Record<string, string>>;
  };
};

const RESEARCH_ARTIFACTS_METADATA_KEY = 'learnitiveResearchArtifacts';
const encoder = new TextEncoder();

export async function getGeneratedResearchArtifacts(
  workspaceOps: WorkspaceOpsService,
  wsName: string,
): Promise<readonly GeneratedResearchArtifact[]> {
  const metadata = await workspaceOps.getWorkspaceMetadata(wsName);
  const value = metadata[RESEARCH_ARTIFACTS_METADATA_KEY];
  if (!value || typeof value !== 'object') return [];
  const items = (value as { items?: unknown }).items;
  if (!Array.isArray(items)) return [];
  return items.filter((item): item is GeneratedResearchArtifact => {
    if (!item || typeof item !== 'object') return false;
    const record = item as Partial<GeneratedResearchArtifact>;
    return typeof record.path === 'string' && typeof record.createdAt === 'number' &&
      ['plot', 'dataset', 'table', 'report', 'binary', 'other'].includes(String(record.kind));
  });
}

export async function writeCanonicalProjectFile(
  dependencies: {
    fileSystem: FileSystemService;
    latexSource: LatexSourceService;
  },
  wsName: string,
  projectPath: string,
  data: Uint8Array | string,
): Promise<void> {
  const normalized = projectPath.replace(/^\/+/, '');
  if (!normalized || normalized.split('/').some((segment) => segment === '..')) {
    throw new Error('Expected a safe project-relative file path');
  }
  const wsPath = WsPath.fromParts(wsName, normalized).wsPath;
  const filePath = WsPath.assertFile(wsPath);
  const bytes = typeof data === 'string' ? encoder.encode(data) : data;
  const file = new File([copyBytesToArrayBuffer(bytes)], filePath.fileName, {
    type: 'application/octet-stream',
  });
  const loadedSource = dependencies.latexSource.getSnapshot(wsPath);
  if (typeof data === 'string' && loadedSource && isLearnitiveEditableTextPath(filePath.path)) {
    dependencies.latexSource.update(wsPath, data, 'terminal');
    await dependencies.latexSource.flush(wsPath);
    return;
  }
  if (await dependencies.fileSystem.exists(wsPath)) {
    await dependencies.fileSystem.writeFile(wsPath, file);
  } else {
    await dependencies.fileSystem.createFile(wsPath, file);
  }
}

export async function writeGeneratedResearchArtifact(
  dependencies: {
    fileSystem: FileSystemService;
    latexSource: LatexSourceService;
    workspaceOps: WorkspaceOpsService;
  },
  wsName: string,
  projectPath: string,
  data: Uint8Array | string,
  artifact: Omit<GeneratedResearchArtifact, 'path' | 'createdAt'>,
): Promise<GeneratedResearchArtifact> {
  const path = projectPath.replace(/^\/+/, '');
  await writeCanonicalProjectFile(dependencies, wsName, path, data);
  const record: GeneratedResearchArtifact = {
    path,
    kind: artifact.kind,
    createdAt: Date.now(),
    ...(artifact.generatedBy ? { generatedBy: artifact.generatedBy } : {}),
  };
  const current = await getGeneratedResearchArtifacts(dependencies.workspaceOps, wsName);
  const next = [...current.filter((item) => item.path !== record.path), record].slice(-500);
  await dependencies.workspaceOps.updateWorkspaceMetadata(wsName, (metadata) => ({
    ...metadata,
    [RESEARCH_ARTIFACTS_METADATA_KEY]: { version: 1, items: next },
  }));
  return record;
}
