// src/lisp/primitives/list.ts
// List Primitives: map, filter, reduce, append, reverse, length, nth, etc.

import type { SExpr } from '../SExpr';
import type { ILambdaClosure } from '../ILambdaClosure';
import type { IPrimitiveDefinition } from './mccarthy';

// Helper type for lambda application
type ApplyLambda = (fn: ILambdaClosure, args: unknown[]) => Promise<unknown>;

/**
 * LIST - Create a list from arguments
 * (list 1 2 3) → (1 2 3)
 */
export const list: IPrimitiveDefinition = {
  name: 'list',
  handler: async (args, evaluate) => {
    const results: unknown[] = [];
    for (const arg of args) {
      results.push(await evaluate(arg));
    }
    return results;
  },
};

/**
 * LENGTH - Get length of list or string
 * (length '(a b c)) → 3
 */
export const length: IPrimitiveDefinition = {
  name: 'length',
  aliases: ['len'],
  handler: async (args, evaluate) => {
    const val = await evaluate(args[0]) as { length?: number };
    return val?.length ?? 0;
  },
};

/**
 * NTH - Get nth element (0-indexed)
 * (nth 1 '(a b c)) → b
 */
export const nth: IPrimitiveDefinition = {
  name: 'nth',
  handler: async (args, evaluate) => {
    const n = await evaluate(args[0]) as number;
    const list = await evaluate(args[1]) as unknown[];
    return Array.isArray(list) ? list[n] : undefined;
  },
};

/**
 * LAST - Get last element
 * (last '(a b c)) → c
 */
export const last: IPrimitiveDefinition = {
  name: 'last',
  handler: async (args, evaluate) => {
    const list = await evaluate(args[0]) as unknown[];
    return Array.isArray(list) && list.length > 0 ? list[list.length - 1] : null;
  },
};

/**
 * BUTLAST - All but last element
 * (butlast '(a b c)) → (a b)
 */
export const butlast: IPrimitiveDefinition = {
  name: 'butlast',
  aliases: ['init'],
  handler: async (args, evaluate) => {
    const list = await evaluate(args[0]) as unknown[];
    return Array.isArray(list) ? list.slice(0, -1) : [];
  },
};

/**
 * APPEND - Concatenate lists
 * (append '(a b) '(c d)) → (a b c d)
 */
export const append: IPrimitiveDefinition = {
  name: 'append',
  handler: async (args, evaluate) => {
    const lists = await Promise.all(args.map(a => evaluate(a))) as unknown[][];
    return lists.flat();
  },
};

/**
 * REVERSE - Reverse a list
 * (reverse '(a b c)) → (c b a)
 */
export const reverse: IPrimitiveDefinition = {
  name: 'reverse',
  handler: async (args, evaluate) => {
    const list = await evaluate(args[0]) as unknown[];
    return Array.isArray(list) ? [...list].reverse() : list;
  },
};

/**
 * MEMBER - Check if element is in list
 * (member 'b '(a b c)) → true
 */
export const member: IPrimitiveDefinition = {
  name: 'member',
  aliases: ['member?', 'includes?'],
  handler: async (args, evaluate) => {
    const item = await evaluate(args[0]);
    const list = await evaluate(args[1]) as unknown[];
    return Array.isArray(list) && list.includes(item);
  },
};

/**
 * RANGE - Generate a range of numbers
 * (range 5) → (0 1 2 3 4)
 * (range 2 5) → (2 3 4)
 * (range 0 10 2) → (0 2 4 6 8)
 */
export const range: IPrimitiveDefinition = {
  name: 'range',
  handler: async (args, evaluate) => {
    const start = args.length > 1 ? await evaluate(args[0]) as number : 0;
    const end = args.length > 1 ? await evaluate(args[1]) as number : await evaluate(args[0]) as number;
    const step = args.length > 2 ? await evaluate(args[2]) as number : 1;
    const result = [];
    for (let i = start; step > 0 ? i < end : i > end; i += step) {
      result.push(i);
    }
    return result;
  },
};

/**
 * TAKE - Take first n elements
 * (take 2 '(a b c d)) → (a b)
 */
export const take: IPrimitiveDefinition = {
  name: 'take',
  handler: async (args, evaluate) => {
    const n = await evaluate(args[0]) as number;
    const list = await evaluate(args[1]) as unknown[];
    return Array.isArray(list) ? list.slice(0, n) : [];
  },
};

/**
 * DROP - Drop first n elements
 * (drop 2 '(a b c d)) → (c d)
 */
export const drop: IPrimitiveDefinition = {
  name: 'drop',
  handler: async (args, evaluate) => {
    const n = await evaluate(args[0]) as number;
    const list = await evaluate(args[1]) as unknown[];
    return Array.isArray(list) ? list.slice(n) : [];
  },
};

/**
 * SORT - Sort a list
 * (sort '(3 1 4 1 5)) → (1 1 3 4 5)
 */
export const sort: IPrimitiveDefinition = {
  name: 'sort',
  handler: async (args, evaluate) => {
    const list = await evaluate(args[0]) as unknown[];
    if (!Array.isArray(list)) return list;
    return [...list].sort((a, b) => (a as number) < (b as number) ? -1 : (a as number) > (b as number) ? 1 : 0);
  },
};

/**
 * ZIP - Zip multiple lists together
 * (zip '(1 2 3) '(a b c)) → ((1 a) (2 b) (3 c))
 */
export const zip: IPrimitiveDefinition = {
  name: 'zip',
  handler: async (args, evaluate) => {
    const lists = await Promise.all(args.map(a => evaluate(a))) as unknown[][];
    if (lists.length === 0) return [];
    const minLen = Math.min(...lists.map(l => Array.isArray(l) ? l.length : 0));
    const result = [];
    for (let i = 0; i < minLen; i++) {
      result.push(lists.map(l => l[i]));
    }
    return result;
  },
};

/**
 * UNIQUE - Remove duplicates
 * (unique '(1 2 2 3 3 3)) → (1 2 3)
 */
export const unique: IPrimitiveDefinition = {
  name: 'unique',
  aliases: ['distinct'],
  handler: async (args, evaluate) => {
    const list = await evaluate(args[0]) as unknown[];
    return Array.isArray(list) ? [...new Set(list)] : list;
  },
};

/**
 * FLATTEN - Flatten nested lists
 * (flatten '((1 2) (3 (4 5)))) → (1 2 3 4 5)
 */
export const flatten: IPrimitiveDefinition = {
  name: 'flatten',
  handler: async (args, evaluate) => {
    const list = await evaluate(args[0]) as unknown[];
    const depth = args.length > 1 ? await evaluate(args[1]) as number : Infinity;
    return Array.isArray(list) ? list.flat(depth) : [list];
  },
};

/**
 * NULL? - Is it null or empty list?
 * (null? '()) → true
 */
export const nullp: IPrimitiveDefinition = {
  name: 'null?',
  aliases: ['nil?', 'empty?'],
  handler: async (args, evaluate) => {
    const val = await evaluate(args[0]);
    return val === null || val === undefined || (Array.isArray(val) && val.length === 0);
  },
};

/**
 * PAIR? - Is it a non-empty list?
 * (pair? '(a b)) → true
 */
export const pairp: IPrimitiveDefinition = {
  name: 'pair?',
  aliases: ['cons?'],
  handler: async (args, evaluate) => {
    const val = await evaluate(args[0]);
    return Array.isArray(val) && val.length >= 1;
  },
};

/**
 * LIST? - Is it a list?
 * (list? '(a b)) → true
 */
export const listp: IPrimitiveDefinition = {
  name: 'list?',
  aliases: ['array?'],
  handler: async (args, evaluate) => {
    const val = await evaluate(args[0]);
    return Array.isArray(val);
  },
};

/**
 * All list primitives
 */
export const listPrimitives: IPrimitiveDefinition[] = [
  list,
  length,
  nth,
  last,
  butlast,
  append,
  reverse,
  member,
  range,
  take,
  drop,
  sort,
  zip,
  unique,
  flatten,
  nullp,
  pairp,
  listp,
];
