import type { Mission, Project, Task, MissionStatus, TaskPriority, TaskStatus } from '@/lib/types';
import type { FreshPayload } from './model';

/**
 * Runtime schema validators for gateway collections (RI-5-001).
 *
 * `api<T>()` returns untrusted JSON cast to `T`; these validators are the
 * seam where a malformed response becomes an explicit schema mismatch
 * instead of flowing into the render path as if it were healthy data.
 */

const taskStatuses: readonly TaskStatus[] = [
  'not-started',
  'in-progress',
  'blocked',
  'done',
  'cancelled',
];
const taskPriorities: readonly TaskPriority[] = ['critical', 'high', 'medium', 'low'];
const missionStatuses: readonly MissionStatus[] = [
  'planning',
  'active',
  'paused',
  'completed',
  'failed',
];
const projectStatuses: readonly Project['status'][] = ['active', 'paused', 'completed', 'archived'];

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function isNullableString(value: unknown): value is string | null {
  return value === null || typeof value === 'string';
}

function isOneOf<T extends string>(value: unknown, allowed: readonly T[]): value is T {
  return typeof value === 'string' && (allowed as readonly string[]).includes(value);
}

function isNullableRecord(value: unknown): value is Record<string, unknown> | null {
  return value === null || isRecord(value);
}

function isNullableStringArray(value: unknown): value is string[] | null {
  if (value === null) return true;
  if (!Array.isArray(value)) return false;
  return value.every((item) => typeof item === 'string');
}

function isIsoLike(value: unknown): value is string {
  return typeof value === 'string' && value.length > 0;
}

function isTask(value: unknown): value is Task {
  if (!isRecord(value)) return false;
  return (
    isString(value['id']) &&
    isString(value['title']) &&
    isOneOf(value['status'], taskStatuses) &&
    isOneOf(value['priority'], taskPriorities) &&
    isNullableString(value['projectId']) &&
    isNullableString(value['missionId']) &&
    isNullableString(value['assignee']) &&
    isNullableStringArray(value['tags']) &&
    isNullableRecord(value['metadata']) &&
    isNullableString(value['dueDate']) &&
    isIsoLike(value['createdAt']) &&
    isIsoLike(value['updatedAt'])
  );
}

/** Tasks carry no workspace identity; scope falls back to the policy. */
export function validateTaskCollection(value: unknown): FreshPayload<Task[]> | null {
  if (!Array.isArray(value) || !value.every(isTask)) return null;
  return { data: value as Task[], workspace: null };
}

function isMission(value: unknown): value is Mission {
  if (!isRecord(value)) return false;
  return (
    isString(value['id']) &&
    isString(value['name']) &&
    isOneOf(value['status'], missionStatuses) &&
    isNullableString(value['projectId']) &&
    isNullableString(value['description']) &&
    isNullableRecord(value['metadata']) &&
    isIsoLike(value['createdAt']) &&
    isIsoLike(value['updatedAt'])
  );
}

/** Missions carry no workspace identity; scope falls back to the policy. */
export function validateMissionCollection(value: unknown): FreshPayload<Mission[]> | null {
  if (!Array.isArray(value) || !value.every(isMission)) return null;
  return { data: value as Mission[], workspace: null };
}

function isProject(value: unknown): value is Project {
  if (!isRecord(value)) return false;
  return (
    isString(value['id']) &&
    isString(value['name']) &&
    isOneOf(value['status'], projectStatuses) &&
    isString(value['userId']) &&
    isNullableString(value['description']) &&
    isNullableRecord(value['metadata']) &&
    isIsoLike(value['createdAt']) &&
    isIsoLike(value['updatedAt'])
  );
}

/**
 * Projects are workspace-scoped: every item must carry the same `userId`.
 * A collection mixing identities (cross-workspace leak) is a schema
 * mismatch; the uniform `userId` becomes the snapshot workspace.
 */
export function validateProjectCollection(value: unknown): FreshPayload<Project[]> | null {
  if (!Array.isArray(value) || !value.every(isProject)) return null;
  const projects = value as Project[];
  const workspaces = new Set(projects.map((project) => project.userId));
  if (workspaces.size > 1) return null;
  return { data: projects, workspace: projects.length > 0 ? projects[0]!.userId : null };
}

/** Single project entity (project detail primary collection). */
export function validateProjectEntity(value: unknown): FreshPayload<Project> | null {
  if (!isProject(value)) return null;
  const project = value as Project;
  return { data: project, workspace: project.userId };
}
