/**
 * Memory Store Step Conventions — generates interpreter functions targeting DynamicModelStore.
 *
 * Each convention generates a TypeScript async function body that operates
 * on the RuntimeContext (entity, store, eventBus, params).
 *
 * Used by the app-demo build to produce generated-behaviors.ts.
 * The runtime behavior interpreter calls these functions instead of
 * hand-coding each pattern.
 */

import { CONVENTION_PATTERNS } from '../shared-patterns.js';

export interface MemoryStepConvention {
  name: string;
  pattern: RegExp;
  /** Generate the async function body as a string */
  generateBody: (matchVars: string) => string;
}

/**
 * Generate the complete interpreter module source code.
 * This is called at build time to produce generated-behaviors.ts.
 */
export function generateInterpreterModule(): string {
  const lines: string[] = [];

  lines.push(`/**
 * Generated Behavior Functions — targeting DynamicModelStore
 *
 * AUTO-GENERATED by realize engine memory factory.
 * DO NOT EDIT — modify step-conventions-memory.ts instead.
 *
 * Convention count: ${CONVENTION_PATTERNS.length}
 */

import type { DynamicModelStore, Entity } from '../stores/dynamic-model-store.js';
import type { EventBus } from './event-bus.js';

export interface RuntimeContext {
  modelName: string;
  store: DynamicModelStore;
  eventBus: EventBus;
  entity?: Entity;
  params: Record<string, any>;
  operationName: string;
}

/** Resolve entity ID from params — tries id, modelId (e.g. pollId), entity */
function resolveId(model: string, ctx: RuntimeContext): string | undefined {
  const modelIdKey = model.charAt(0).toLowerCase() + model.slice(1) + 'Id';
  return ctx.params.id || ctx.params[modelIdKey] || ctx.entity?.id;
}

export interface ConventionHandler {
  name: string;
  pattern: RegExp;
  execute: (match: RegExpMatchArray, ctx: RuntimeContext) => Promise<any>;
}

export const GENERATED_CONVENTIONS: ConventionHandler[] = [
`);

  // Generate each convention
  lines.push(generateValidate());
  lines.push(generateCheckNoExisting());
  lines.push(generateCheck());
  lines.push(generateFind());
  lines.push(generateFindAllFor());
  lines.push(generateCountPer());
  lines.push(generateCreate());
  lines.push(generateUpdateField());
  lines.push(generateUpdate());
  lines.push(generateDelete());
  lines.push(generateTransition());
  lines.push(generateSet());
  lines.push(generateIncrement());
  lines.push(generateDecrement());
  lines.push(generateCalculate());
  lines.push(generateSendEvent());
  lines.push(generateSendNotification());
  lines.push(generateCallService());
  lines.push(generateReturn());

  lines.push(`];

/**
 * Match a step against conventions and execute.
 * Returns { executed, skipped, result }.
 */
export async function executeStep(
  step: string,
  ctx: RuntimeContext
): Promise<{ matched: boolean; convention?: string; result?: any; error?: string }> {
  for (const convention of GENERATED_CONVENTIONS) {
    const match = step.match(convention.pattern);
    if (match) {
      try {
        const result = await convention.execute(match, ctx);
        return { matched: true, convention: convention.name, result };
      } catch (error: any) {
        return { matched: true, convention: convention.name, error: error.message };
      }
    }
  }
  return { matched: false };
}
`);

  return lines.join('\n');
}

// ── Individual convention generators ──────────────────────────────────────

function p(pattern: PatternDef): string {
  // Convert RegExp to source string for code generation
  const src = CONVENTION_PATTERNS.find(p => p.name === pattern)?.pattern.source || '';
  const flags = CONVENTION_PATTERNS.find(p => p.name === pattern)?.pattern.flags || 'i';
  return `/${src}/${flags}`;
}

type PatternDef = string;

function generateValidate(): string {
  return `  {
    name: 'validate',
    pattern: ${p('validate')},
    execute: async (m, ctx) => {
      const target = m[1];
      const data = ctx.params.data || ctx.params;
      if (!data || Object.keys(data).length === 0) {
        throw new Error(\`Validation failed: no data provided for \${target}\`);
      }
      return { valid: true, target };
    },
  },`;
}

function generateCheckNoExisting(): string {
  return `  {
    name: 'check-no-existing',
    pattern: ${p('check-no-existing')},
    execute: async (m, ctx) => {
      const checkModel = m[1].charAt(0).toUpperCase() + m[1].slice(1);
      const entityA = m[2].charAt(0).toUpperCase() + m[2].slice(1);
      const entityB = m[3].charAt(0).toUpperCase() + m[3].slice(1);
      const fkA = entityA.charAt(0).toLowerCase() + entityA.slice(1) + 'Id';
      const fkB = entityB.charAt(0).toLowerCase() + entityB.slice(1) + 'Id';
      const idA = ctx.params[fkA] || resolveId(entityA, ctx);
      const idB = ctx.params[fkB] || resolveId(entityB, ctx);
      if (!idA || !idB) return true;
      const all = await ctx.store.query(checkModel, {});
      const existing = all.entities.find((e: any) =>
        (e.data?.[fkA] === idA) && (e.data?.[fkB] === idB)
      );
      if (existing) {
        throw new Error(\`\${entityA} already has a \${checkModel} for this \${entityB}\`);
      }
      return true;
    },
  },`;
}

function generateCheck(): string {
  return `  {
    name: 'check',
    pattern: ${p('check')},
    execute: async (m, ctx) => {
      const condition = m[1];
      if (ctx.entity) {
        const data = ctx.entity.data || {};
        const fieldMatch = condition.match(/(\\w+)/i);
        if (fieldMatch && data[fieldMatch[1]] !== undefined) {
          return !!data[fieldMatch[1]];
        }
      }
      return true;
    },
  },`;
}

function generateFind(): string {
  return `  {
    name: 'find',
    pattern: ${p('find')},
    execute: async (m, ctx) => {
      const model = m[1];
      const field = m[2];
      if (field === 'id') {
        const id = resolveId(model, ctx);
        if (!id) throw new Error(\`No id to find \${model}\`);
        const entity = await ctx.store.retrieve(model, id);
        if (!entity) throw new Error(\`\${model} not found by id: \${id}\`);
        return entity;
      }
      const value = ctx.params[field] || ctx.params.data?.[field];
      const result = await ctx.store.query(model, { filters: { [field]: value }, limit: 1 });
      if (result.entities.length === 0) throw new Error(\`\${model} not found by \${field}: \${value}\`);
      return result.entities[0];
    },
  },`;
}

function generateFindAllFor(): string {
  return `  {
    name: 'find-all-for',
    pattern: ${p('find-all-for')},
    execute: async (m, ctx) => {
      const targetModel = m[1].charAt(0).toUpperCase() + m[1].slice(1);
      const sourceModel = m[2].charAt(0).toUpperCase() + m[2].slice(1);
      const sourceId = resolveId(sourceModel, ctx);
      if (!sourceId) throw new Error(\`No id to find \${targetModel}s for \${sourceModel}\`);
      const fk = sourceModel.charAt(0).toLowerCase() + sourceModel.slice(1) + 'Id';
      const result = await ctx.store.query(targetModel, { filters: { [fk]: sourceId } });
      if (result.entities.length === 0) {
        const allResult = await ctx.store.query(targetModel, {});
        return allResult.entities.filter((e: any) => e.data?.[fk] === sourceId);
      }
      return result.entities;
    },
  },`;
}

function generateCountPer(): string {
  return `  {
    name: 'count-per',
    pattern: ${p('count-per')},
    execute: async (m, ctx) => {
      const countModel = m[1].charAt(0).toUpperCase() + m[1].slice(1);
      const groupModel = m[2].charAt(0).toUpperCase() + m[2].slice(1);
      const groupFk = groupModel.charAt(0).toLowerCase() + groupModel.slice(1) + 'Id';
      const allResult = await ctx.store.query(countModel, {});
      const counts: Record<string, number> = {};
      for (const entity of allResult.entities) {
        const groupKey = entity.data?.[groupFk] || 'unknown';
        counts[groupKey] = (counts[groupKey] || 0) + 1;
      }
      return counts;
    },
  },`;
}

function generateCreate(): string {
  return `  {
    name: 'create',
    pattern: ${p('create')},
    execute: async (m, ctx) => {
      const model = m[1];
      const data = ctx.params.data || ctx.params;
      return await ctx.store.create(model, data);
    },
  },`;
}

function generateUpdateField(): string {
  return `  {
    name: 'update-field',
    pattern: ${p('update-field')},
    execute: async (m, ctx) => {
      const model = m[1];
      const field = m[2];
      let value: any = m[3].trim();
      if (value === 'current time' || value === 'now') value = new Date().toISOString();
      const id = resolveId(model, ctx);
      if (!id) throw new Error(\`No id to update \${model}.\${field}\`);
      // Check if field is a lifecycle — use evolveLifecycle (no data validation)
      const entity = await ctx.store.retrieve(model, id);
      if (entity?.metadata?.lifecycleStates && field in entity.metadata.lifecycleStates) {
        return await ctx.store.evolveLifecycle(model, id, field, value);
      }
      return await ctx.store.update(model, id, { [field]: value });
    },
  },`;
}

function generateUpdate(): string {
  return `  {
    name: 'update',
    pattern: ${p('update')},
    execute: async (m, ctx) => {
      const model = m[1];
      const id = resolveId(model, ctx);
      const data = ctx.params.data || ctx.params;
      if (!id) throw new Error(\`No id to update \${model}\`);
      return await ctx.store.update(model, id, data);
    },
  },`;
}

function generateDelete(): string {
  return `  {
    name: 'delete',
    pattern: ${p('delete')},
    execute: async (m, ctx) => {
      const model = m[1];
      const id = resolveId(model, ctx);
      if (!id) throw new Error(\`No id to delete \${model}\`);
      await ctx.store.delete(model, id);
      return { deleted: true };
    },
  },`;
}

function generateTransition(): string {
  return `  {
    name: 'transition',
    pattern: ${p('transition')},
    execute: async (m, ctx) => {
      const model = m[1];
      const state = m[2];
      const id = resolveId(model, ctx);
      if (!id) throw new Error(\`No id to transition \${model}\`);
      const entity = await ctx.store.retrieve(model, id);
      if (!entity) throw new Error(\`\${model} not found for transition\`);
      const lifecycleName = Object.keys(entity.metadata?.lifecycleStates || {})[0];
      if (lifecycleName) {
        return await ctx.store.evolveLifecycle(model, id, lifecycleName, state);
      }
      throw new Error(\`No lifecycle found on \${model} to transition\`);
    },
  },`;
}

function generateSet(): string {
  return `  {
    name: 'set',
    pattern: ${p('set')},
    execute: async (m, ctx) => {
      const field = m[1];
      let value: any = m[2].trim();
      if (/current time|now|timestamp/i.test(value)) value = new Date().toISOString();
      const id = resolveId(ctx.modelName, ctx);
      if (!id) throw new Error(\`No id to set \${field}\`);
      return await ctx.store.update(ctx.modelName, id, { [field]: value });
    },
  },`;
}

function generateIncrement(): string {
  return `  {
    name: 'increment',
    pattern: ${p('increment')},
    execute: async (m, ctx) => {
      const field = m[1];
      const amount = /^\\d+$/.test(m[2]) ? parseInt(m[2]) : (ctx.params[m[2]] || 1);
      const id = resolveId(ctx.modelName, ctx);
      if (!id) throw new Error(\`No id to increment \${field}\`);
      const entity = await ctx.store.retrieve(ctx.modelName, id);
      if (!entity) throw new Error(\`Entity not found to increment \${field}\`);
      const current = (entity.data[field] || 0) as number;
      return await ctx.store.update(ctx.modelName, id, { [field]: current + amount });
    },
  },`;
}

function generateDecrement(): string {
  return `  {
    name: 'decrement',
    pattern: ${p('decrement')},
    execute: async (m, ctx) => {
      const field = m[1];
      const amount = /^\\d+$/.test(m[2]) ? parseInt(m[2]) : (ctx.params[m[2]] || 1);
      const id = resolveId(ctx.modelName, ctx);
      if (!id) throw new Error(\`No id to decrement \${field}\`);
      const entity = await ctx.store.retrieve(ctx.modelName, id);
      if (!entity) throw new Error(\`Entity not found to decrement \${field}\`);
      const current = (entity.data[field] || 0) as number;
      if (current < amount) throw new Error(\`Cannot decrement \${field} below zero\`);
      return await ctx.store.update(ctx.modelName, id, { [field]: current - amount });
    },
  },`;
}

function generateCalculate(): string {
  return `  {
    name: 'calculate',
    pattern: ${p('calculate')},
    execute: async (m, ctx) => {
      const metric = m[1].toLowerCase();
      if (metric.includes('percentage') || metric.includes('percent')) {
        const counts = ctx.params._lastResult;
        if (counts && typeof counts === 'object') {
          const total = Object.values(counts as Record<string, number>).reduce((a: number, b: number) => a + b, 0);
          if (total > 0) {
            const pcts: Record<string, string> = {};
            for (const [key, count] of Object.entries(counts as Record<string, number>)) {
              pcts[key] = \`\${((count / total) * 100).toFixed(1)}%\`;
            }
            return pcts;
          }
        }
      }
      if (metric.includes('winner') || metric.includes('max') || metric.includes('highest')) {
        const counts = ctx.params._lastResult;
        if (counts && typeof counts === 'object') {
          let maxKey = '', maxVal = -1;
          for (const [key, val] of Object.entries(counts as Record<string, number>)) {
            if (val > maxVal) { maxVal = val; maxKey = key; }
          }
          return { winner: maxKey, count: maxVal };
        }
      }
      return 0;
    },
  },`;
}

function generateSendEvent(): string {
  return `  {
    name: 'send-event',
    pattern: ${p('send-event')},
    execute: async (m, ctx) => {
      const event = m[1];
      await ctx.eventBus.publish(event, {
        modelName: ctx.modelName,
        entityId: ctx.entity?.id || ctx.params.id,
        operation: ctx.operationName,
        timestamp: new Date().toISOString(),
      });
      return { event, published: true };
    },
  },`;
}

function generateSendNotification(): string {
  return `  {
    name: 'send-notification',
    pattern: ${p('send-notification')},
    execute: async (m, ctx) => {
      const type = m[1];
      await ctx.eventBus.publish(\`\${type}Notification\`, {
        modelName: ctx.modelName,
        entityId: ctx.entity?.id || ctx.params.id,
        operation: ctx.operationName,
      });
      return { notification: type, sent: true };
    },
  },`;
}

function generateCallService(): string {
  return `  {
    name: 'call-service',
    pattern: ${p('call-service')},
    execute: async (m, ctx) => {
      const service = m[1];
      const method = m[2];
      await ctx.eventBus.publish(\`\${service}.\${method}\`, ctx.params);
      return { delegated: true, service, method };
    },
  },`;
}

function generateReturn(): string {
  return `  {
    name: 'return',
    pattern: ${p('return')},
    execute: async (m, ctx) => {
      // Return the last computed result if available, otherwise entity or literal
      if (ctx.params._lastResult !== undefined) return ctx.params._lastResult;
      if (ctx.entity) return ctx.entity;
      return { value: m[1].trim() };
    },
  },`;
}
