import { GlobalStateStore } from './shellState';

function authHeaders(): HeadersInit {
  const token = GlobalStateStore.getState().session.authToken;
  return token ? { Authorization: `Bearer ${token}` } : {};
}

type ResolveSpecResponse<T> = {
  success: boolean;
  data: T;
  metadata?: unknown;
  error?: { code: string; message: string; detail?: string };
};

type ResolveSpecFilter = {
  column: string;
  operator: string;
  value?: unknown;
};

function normalizeTags(value: unknown): string[] {
  if (Array.isArray(value)) {
    return value.map((tag) => String(tag).trim()).filter(Boolean);
  }
  if (typeof value !== 'string') {
    return [];
  }

  const trimmed = value.trim();
  if (!trimmed) return [];

  // Handle Postgres text[] wire shape: "{tag1,tag2}".
  if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
    return trimmed
      .slice(1, -1)
      .split(',')
      .map((tag) => tag.trim().replace(/^"(.*)"$/, '$1'))
      .filter(Boolean);
  }

  if (trimmed.includes(',')) {
    return trimmed
      .split(',')
      .map((tag) => tag.trim())
      .filter(Boolean);
  }

  return [trimmed];
}

async function get<T>(path: string): Promise<T> {
  const res = await fetch(path, { headers: authHeaders() });
  if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
  return res.json() as Promise<T>;
}

async function post<T>(path: string, body: unknown): Promise<T> {
  const res = await fetch(path, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', ...authHeaders() },
    body: JSON.stringify(body)
  });
  if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
  if (res.status === 204) return undefined as T;
  return res.json() as Promise<T>;
}

async function del(path: string): Promise<void> {
  const res = await fetch(path, { method: 'DELETE', headers: authHeaders() });
  if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
}

async function rsCall<T>(
  path: string,
  operation: 'read' | 'create' | 'update' | 'delete',
  payload?: { data?: unknown; options?: unknown }
): Promise<T> {
  const res = await fetch(path, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', ...authHeaders() },
    body: JSON.stringify({
      operation,
      ...(payload?.data !== undefined ? { data: payload.data } : {}),
      ...(payload?.options !== undefined ? { options: payload.options } : {})
    })
  });
  if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
  const body = (await res.json()) as ResolveSpecResponse<T>;
  if (!body.success) {
    throw new Error(body.error?.message ?? 'ResolveSpec request failed');
  }
  return body.data;
}

function rsReadMany<T>(
  entity: string,
  options?: { filters?: ResolveSpecFilter[]; limit?: number; offset?: number; sort?: { column: string; direction: 'asc' | 'desc' }[] }
): Promise<T[]> {
  return rsCall<T[] | null>(`/api/rs/public/${entity}`, 'read', {
    options: {
      ...(options?.filters?.length ? { filters: options.filters } : {}),
      ...(options?.sort?.length ? { sort: options.sort } : {}),
      ...(options?.limit ? { limit: options.limit } : {}),
      ...(options?.offset ? { offset: options.offset } : {})
    }
  }).then((rows) => (Array.isArray(rows) ? rows : []));
}

export const api = {
  projects: {
    list: async (params?: { limit?: number; offset?: number; q?: string }) => {
      type ProjectRow = {
        guid: string;
        name: string;
        description: string | null;
        created_at: string;
        last_active_at: string;
        thought_count?: number;
      };

      const filters: ResolveSpecFilter[] = [];
      if (params?.q) {
        filters.push({ column: 'name', operator: 'ilike', value: `%${params.q}%` });
      }

      const projectRows = await rsCall<ProjectRow[] | null>('/api/rs/public/projects', 'read', {
        options: {
          columns: ['guid', 'name', 'description', 'created_at', 'last_active_at'],
          computedColumns: [
            {
              name: 'thought_count',
              expression: 'COALESCE((SELECT COUNT(*) FROM public.thoughts t WHERE t.project_id = projects.guid), 0)'
            }
          ],
          ...(filters.length ? { filters } : {}),
          sort: [{ column: 'created_at', direction: 'desc' }],
          limit: params?.limit ?? 100,
          offset: params?.offset ?? 0
        }
      });

      const projects = Array.isArray(projectRows) ? projectRows : [];
      return projects.map((project) => ({
        id: project.guid,
        name: project.name,
        description: project.description ?? '',
        created_at: project.created_at,
        last_active_at: project.last_active_at,
        thought_count: project.thought_count ?? 0
      }));
    },
    create: (name: string, description: string) =>
      rsCall<import('./types').Project>('/api/rs/public/projects', 'create', {
        data: { name, description }
      }),
    update: (id: string, data: { name?: string; description?: string }) =>
      rsCall<void>(`/api/rs/public/projects/${id}`, 'update', { data }),
    delete: (id: string) => rsCall<void>(`/api/rs/public/projects/${id}`, 'delete')
  },
  thoughts: {
    list: (params: { q?: string; project_id?: string; limit?: number; offset?: number; include_archived?: boolean }) => {
      const filters: ResolveSpecFilter[] = [];
      if (params.q) {
        filters.push({ column: 'content', operator: 'ilike', value: `%${params.q}%` });
      }
      if (params.project_id) {
        filters.push({ column: 'project_id', operator: 'eq', value: params.project_id });
      }
      if (!params.include_archived) {
        filters.push({ column: 'archived_at', operator: 'empty' });
      }
      return rsReadMany<import('./types').Thought>('thoughts', {
        filters,
        limit: params.limit ?? 100,
        ...(params.offset !== undefined ? { offset: params.offset } : {}),
        sort: [{ column: 'created_at', direction: 'desc' }]
      }).then((rows) =>
        rows.map((row) => ({
          ...row,
          content: typeof row.content === 'string' ? row.content : '',
          metadata: {
            ...(row.metadata ?? {}),
            people: row.metadata?.people ?? [],
            action_items: row.metadata?.action_items ?? [],
            dates_mentioned: row.metadata?.dates_mentioned ?? [],
            topics: row.metadata?.topics ?? [],
            type: row.metadata?.type ?? '',
            source: row.metadata?.source ?? '',
            metadata_status: row.metadata?.metadata_status ?? ''
          }
        }))
      );
    },
    links: async (thoughtID: string) => {
      const numericID = Number.parseInt(thoughtID, 10);
      if (Number.isNaN(numericID)) return [];

      const [outbound, inbound] = await Promise.all([
        rsReadMany<import('./types').ThoughtLink>('thought_links', {
          filters: [{ column: 'from_id', operator: 'eq', value: numericID }],
          limit: 200,
          sort: [{ column: 'created_at', direction: 'desc' }]
        }),
        rsReadMany<import('./types').ThoughtLink>('thought_links', {
          filters: [{ column: 'to_id', operator: 'eq', value: numericID }],
          limit: 200,
          sort: [{ column: 'created_at', direction: 'desc' }]
        })
      ]);

      const byID = new Map<number, import('./types').ThoughtLink>();
      for (const link of outbound) byID.set(link.id, link);
      for (const link of inbound) byID.set(link.id, link);

      return Array.from(byID.values()).sort(
        (a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
      );
    },
    get: (id: string) =>
      rsCall<import('./types').Thought>(`/api/rs/public/thoughts/${id}`, 'read').then((row) => ({
        ...row,
        content: typeof row.content === 'string' ? row.content : '',
        metadata: {
          ...(row.metadata ?? {}),
          people: row.metadata?.people ?? [],
          action_items: row.metadata?.action_items ?? [],
          dates_mentioned: row.metadata?.dates_mentioned ?? [],
          topics: row.metadata?.topics ?? [],
          type: row.metadata?.type ?? '',
          source: row.metadata?.source ?? '',
          metadata_status: row.metadata?.metadata_status ?? ''
        }
      })),
    delete: (id: string) => rsCall<void>(`/api/rs/public/thoughts/${id}`, 'delete'),
    archive: (id: string) =>
      rsCall<void>(`/api/rs/public/thoughts/${id}`, 'update', {
        data: { archived_at: new Date().toISOString() }
      }),
    create: (data: { content: string; project_id?: string }) =>
      rsCall<import('./types').Thought>('/api/rs/public/thoughts', 'create', { data }),
    update: (id: string, data: { content?: string }) =>
      rsCall<void>(`/api/rs/public/thoughts/${id}`, 'update', { data })
  },
  skills: {
    list: async (tag?: string) => {
      const rows = await rsReadMany<Omit<import('./types').AgentSkill, 'tags'> & { tags?: unknown }>('agent_skills', {
        filters: tag ? [{ column: 'tags', operator: 'contains', value: tag }] : undefined,
        limit: 500,
        sort: [{ column: 'created_at', direction: 'desc' }]
      });
      return rows.map((row) => ({ ...row, tags: normalizeTags(row.tags) }));
    },
    get: async (id: string) => {
      const row = await rsCall<Omit<import('./types').AgentSkill, 'tags'> & { tags?: unknown }>(
        `/api/rs/public/agent_skills/${id}`,
        'read'
      );
      return { ...row, tags: normalizeTags(row.tags) };
    },
    create: (data: { name: string; description?: string; content: string; tags?: string[] }) =>
      rsCall<import('./types').AgentSkill>('/api/rs/public/agent_skills', 'create', { data }),
    delete: (id: string) => rsCall<void>(`/api/rs/public/agent_skills/${id}`, 'delete'),
    update: (id: string, data: Partial<import('./types').AgentSkill>) =>
      rsCall<void>(`/api/rs/public/agent_skills/${id}`, 'update', { data })
  },
  guardrails: {
    list: async (params?: { tag?: string; severity?: string }) => {
      const filters: ResolveSpecFilter[] = [];
      if (params?.tag) filters.push({ column: 'tags', operator: 'contains', value: params.tag });
      if (params?.severity) filters.push({ column: 'severity', operator: 'eq', value: params.severity });

      const rows = await rsReadMany<Omit<import('./types').AgentGuardrail, 'tags'> & { tags?: unknown }>('agent_guardrails', {
        filters,
        limit: 500,
        sort: [{ column: 'created_at', direction: 'desc' }]
      });
      return rows.map((row) => ({ ...row, tags: normalizeTags(row.tags) }));
    },
    delete: (id: string) => rsCall<void>(`/api/rs/public/agent_guardrails/${id}`, 'delete'),
    update: (id: string, data: Partial<import('./types').AgentGuardrail>) =>
      rsCall<void>(`/api/rs/public/agent_guardrails/${id}`, 'update', { data })
  },
  files: {
    list: (params?: { project_id?: string; thought_id?: string; kind?: string }) => {
      const filters: ResolveSpecFilter[] = [];
      if (params?.project_id) filters.push({ column: 'project_id', operator: 'eq', value: params.project_id });
      if (params?.thought_id) filters.push({ column: 'thought_id', operator: 'eq', value: params.thought_id });
      if (params?.kind) filters.push({ column: 'kind', operator: 'eq', value: params.kind });
      return rsReadMany<import('./types').StoredFile>('stored_files', {
        filters,
        limit: 500,
        sort: [{ column: 'created_at', direction: 'desc' }]
      });
    }
  },
  maintenance: {
    tasks: () =>
      rsReadMany<import('./types').MaintenanceTask>('maintenance_tasks', {
        limit: 200,
        sort: [{ column: 'next_due', direction: 'asc' }]
      }),
    logs: () =>
      rsReadMany<import('./types').MaintenanceLog>('maintenance_logs', {
        limit: 200,
        sort: [{ column: 'completed_at', direction: 'desc' }]
      }),
    runBackfill: (input?: {
      project?: string;
      limit?: number;
      include_archived?: boolean;
      older_than_days?: number;
      dry_run?: boolean;
    }) =>
      post<import('./types').BackfillResult>('/api/admin/actions/backfill', {
        limit: input?.limit ?? 100,
        project: input?.project,
        include_archived: input?.include_archived ?? false,
        older_than_days: input?.older_than_days ?? 0,
        dry_run: input?.dry_run ?? false
      }),
    runRetryMetadata: (input?: {
      project?: string;
      limit?: number;
      include_archived?: boolean;
      older_than_days?: number;
      dry_run?: boolean;
    }) =>
      post<import('./types').MetadataRetryResult>('/api/admin/actions/retry-metadata', {
        limit: input?.limit ?? 100,
        project: input?.project,
        include_archived: input?.include_archived ?? false,
        older_than_days: input?.older_than_days ?? 1,
        dry_run: input?.dry_run ?? false
      })
  },
  plans: {
    list: async (params?: { status?: string; priority?: string; project_id?: string; limit?: number }) => {
      const filters: ResolveSpecFilter[] = [];
      if (params?.status) filters.push({ column: 'status', operator: 'eq', value: params.status });
      if (params?.priority) filters.push({ column: 'priority', operator: 'eq', value: params.priority });
      if (params?.project_id) filters.push({ column: 'project_id', operator: 'eq', value: params.project_id });

      const rows = await rsReadMany<Omit<import('./types').Plan, 'tags'> & { tags?: unknown }>('plans', {
        filters,
        limit: params?.limit ?? 500,
        sort: [{ column: 'updated_at', direction: 'desc' }]
      });
      return rows.map((row) => ({ ...row, tags: normalizeTags(row.tags) }));
    },
    get: async (id: string) => {
      const row = await rsCall<Omit<import('./types').Plan, 'tags'> & { tags?: unknown }>(
        `/api/rs/public/plans/${id}`,
        'read'
      );
      return { ...row, tags: normalizeTags(row.tags) };
    },
    create: (data: object) =>
      rsCall<import('./types').Plan>('/api/rs/public/plans', 'create', { data }),
    update: (id: string, data: Partial<import('./types').Plan>) =>
      rsCall<void>(`/api/rs/public/plans/${id}`, 'update', { data }),
    delete: (id: string) => rsCall<void>(`/api/rs/public/plans/${id}`, 'delete')
  },
  learnings: {
    list: (params?: { limit?: number; offset?: number }) =>
      rsReadMany<import('./types').Learning>('learnings', {
        limit: params?.limit ?? 500,
        ...(params?.offset !== undefined ? { offset: params.offset } : {}),
        sort: [{ column: 'created_at', direction: 'desc' }]
      }),
    get: (id: string) =>
      rsCall<import('./types').Learning>(`/api/rs/public/learnings/${id}`, 'read'),
    create: (data: object) =>
      rsCall<import('./types').Learning>('/api/rs/public/learnings', 'create', { data }),
    update: (id: string, data: Partial<import('./types').Learning>) =>
      rsCall<void>(`/api/rs/public/learnings/${id}`, 'update', { data }),
    delete: (id: string) => rsCall<void>(`/api/rs/public/learnings/${id}`, 'delete')
  },
  stats: async () => {
    type StatsThoughtRow = {
      metadata?: {
        type?: string;
        topics?: string[];
        people?: string[];
      };
    };

    const thoughts = await rsReadMany<StatsThoughtRow>('thoughts', {
      limit: 5000,
      sort: [{ column: 'created_at', direction: 'desc' }]
    });

    const typeCounts: Record<string, number> = {};
    const topicCounts: Record<string, number> = {};
    const peopleCounts: Record<string, number> = {};

    for (const thought of thoughts) {
      const type = thought.metadata?.type?.trim() || 'unknown';
      typeCounts[type] = (typeCounts[type] ?? 0) + 1;

      for (const topic of thought.metadata?.topics ?? []) {
        const key = topic.trim();
        if (!key) continue;
        topicCounts[key] = (topicCounts[key] ?? 0) + 1;
      }

      for (const person of thought.metadata?.people ?? []) {
        const key = person.trim();
        if (!key) continue;
        peopleCounts[key] = (peopleCounts[key] ?? 0) + 1;
      }
    }

    const topEntries = (map: Record<string, number>) =>
      Object.entries(map)
        .sort((a, b) => b[1] - a[1])
        .slice(0, 10)
        .map(([key, count]) => ({ key, count }));

    const stats: import('./types').ThoughtStats = {
      total_count: thoughts.length,
      type_counts: typeCounts,
      top_topics: topEntries(topicCounts),
      top_people: topEntries(peopleCounts)
    };

    return stats;
  }
};
