/**
 * Lua Runtime using Fengari
 * Executes Lua code with a mock Roblox API
 */

import { createRobloxAPI, Instance } from './roblox-api';

export interface LuaRuntimeOutput {
  type: 'log' | 'warn' | 'error' | 'info';
  message: string;
  timestamp: number;
}

export interface LuaRuntimeState {
  running: boolean;
  outputs: LuaRuntimeOutput[];
  error: string | null;
  instances: Instance[];
}

export interface LuaRuntime {
  execute: (code: string) => Promise<LuaRuntimeState>;
  stop: () => void;
  getState: () => LuaRuntimeState;
  getInstances: () => Instance[];
  onOutput: (callback: (output: LuaRuntimeOutput) => void) => void;
  onInstanceUpdate: (callback: (instances: Instance[]) => void) => void;
}

/**
 * Creates a new Lua runtime instance
 * Uses JavaScript-based Lua transpilation for simplicity
 */
export function createLuaRuntime(): LuaRuntime {
  let state: LuaRuntimeState = {
    running: false,
    outputs: [],
    error: null,
    instances: [],
  };

  let outputCallbacks: ((output: LuaRuntimeOutput) => void)[] = [];
  let instanceCallbacks: ((instances: Instance[]) => void)[] = [];
  let stopFlag = false;
  let api = createRobloxAPI();

  const addOutput = (type: LuaRuntimeOutput['type'], message: string) => {
    const output: LuaRuntimeOutput = {
      type,
      message,
      timestamp: Date.now(),
    };
    state.outputs.push(output);
    outputCallbacks.forEach(cb => cb(output));
  };

  const notifyInstanceUpdate = () => {
    instanceCallbacks.forEach(cb => cb(state.instances));
  };

  /**
   * Simple Lua to JavaScript transpiler for basic Roblox scripts
   * This handles common patterns but isn't a full Lua implementation
   */
  const transpileLuaToJS = (luaCode: string): string => {
    let js = luaCode;

    // Remove comments
    js = js.replace(/--\[\[[\s\S]*?\]\]/g, '');
    js = js.replace(/--.*$/gm, '');

    // Replace local keyword
    js = js.replace(/\blocal\s+/g, 'let ');

    // Replace function definitions
    js = js.replace(/function\s+(\w+)\s*\((.*?)\)/g, 'async function $1($2)');
    js = js.replace(/function\s*\((.*?)\)/g, 'async function($1)');

    // Replace end keywords
    js = js.replace(/\bend\b/g, '}');

    // Replace then/do with {
    js = js.replace(/\bthen\b/g, '{');
    js = js.replace(/\bdo\b/g, '{');

    // Replace elseif with else if
    js = js.replace(/\belseif\b/g, '} else if');

    // Replace else (but not else if)
    js = js.replace(/\belse\b(?!\s*if)/g, '} else {');

    // Replace repeat/until
    js = js.replace(/\brepeat\b/g, 'do {');
    js = js.replace(/\buntil\s+(.*?)$/gm, '} while (!($1));');

    // Replace for loops - numeric
    js = js.replace(
      /for\s+(\w+)\s*=\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d+))?\s*\{/g,
      (_, var_, start, end, step) => {
        const stepVal = step || '1';
        return `for (let ${var_} = ${start}; ${var_} <= ${end}; ${var_} += ${stepVal}) {`;
      }
    );

    // Replace for...in loops (pairs)
    js = js.replace(
      /for\s+(\w+)\s*,\s*(\w+)\s+in\s+pairs\s*\((.*?)\)\s*\{/g,
      'for (let [$1, $2] of Object.entries($3)) {'
    );

    // Replace for...in loops (ipairs)
    js = js.replace(
      /for\s+(\w+)\s*,\s*(\w+)\s+in\s+ipairs\s*\((.*?)\)\s*\{/g,
      'for (let [$1, $2] of $3.entries()) {'
    );

    // Replace while loops
    js = js.replace(/while\s+(.*?)\s*\{/g, 'while ($1) {');

    // Replace if statements
    js = js.replace(/if\s+(.*?)\s*\{/g, 'if ($1) {');

    // Replace logical operators
    js = js.replace(/\band\b/g, '&&');
    js = js.replace(/\bor\b/g, '||');
    js = js.replace(/\bnot\b/g, '!');

    // Replace nil with null
    js = js.replace(/\bnil\b/g, 'null');

    // Replace ~= with !==
    js = js.replace(/~=/g, '!==');

    // Replace string concatenation
    js = js.replace(/\.\./g, '+');

    // Replace # length operator
    js = js.replace(/#(\w+)/g, '$1.length');

    // Replace table constructors
    js = js.replace(/\{([^{}]*?)\}/g, (match, content) => {
      // Check if it looks like an array
      if (!/\w+\s*=/.test(content)) {
        return `[${content}]`;
      }
      // It's an object
      const converted = content.replace(/\[["']?(\w+)["']?\]\s*=/g, '$1:').replace(/(\w+)\s*=/g, '$1:');
      return `{${converted}}`;
    });

    // Replace print with our output function
    js = js.replace(/\bprint\s*\(/g, '__print(');

    // Replace warn
    js = js.replace(/\bwarn\s*\(/g, '__warn(');

    // Replace wait
    js = js.replace(/\bwait\s*\(/g, 'await __wait(');
    js = js.replace(/\btask\.wait\s*\(/g, 'await __wait(');

    // Replace game:GetService
    js = js.replace(/game:GetService\s*\(\s*["'](\w+)["']\s*\)/g, '__game.GetService("$1")');
    js = js.replace(/game\.(\w+)/g, '__game.$1');

    // Replace workspace
    js = js.replace(/\bworkspace\b/g, '__workspace');

    // Replace Instance.new
    js = js.replace(/Instance\.new\s*\(\s*["'](\w+)["']\s*(?:,\s*(.*?))?\)/g, (_, className, parent) => {
      if (parent) {
        return `__Instance.new("${className}", ${parent})`;
      }
      return `__Instance.new("${className}")`;
    });

    // Replace Vector3.new
    js = js.replace(/Vector3\.new\s*\((.*?)\)/g, '__Vector3.new($1)');
    js = js.replace(/Vector3\.zero/g, '__Vector3.zero');
    js = js.replace(/Vector3\.one/g, '__Vector3.one');

    // Replace Color3
    js = js.replace(/Color3\.new\s*\((.*?)\)/g, '__Color3.new($1)');
    js = js.replace(/Color3\.fromRGB\s*\((.*?)\)/g, '__Color3.fromRGB($1)');

    // Replace CFrame.new
    js = js.replace(/CFrame\.new\s*\((.*?)\)/g, '__CFrame.new($1)');

    // Replace TweenInfo.new
    js = js.replace(/TweenInfo\.new\s*\((.*?)\)/g, '__TweenInfo.new($1)');

    // Replace Enum references
    js = js.replace(/Enum\.(\w+)\.(\w+)/g, '__Enum.$1.$2');

    // Replace method calls with : to .
    js = js.replace(/:(\w+)\s*\(/g, '.$1(');

    // Replace self with this
    js = js.replace(/\bself\b/g, 'this');

    // Replace true/false (they're the same in JS)

    // Replace math functions
    js = js.replace(/math\.(\w+)/g, 'Math.$1');

    // Replace string functions
    js = js.replace(/string\.(\w+)/g, (_, fn) => {
      const mapping: Record<string, string> = {
        len: 'length',
        sub: 'substring',
        lower: 'toLowerCase',
        upper: 'toUpperCase',
        find: 'indexOf',
        format: '__stringFormat',
      };
      return mapping[fn] || `String.prototype.${fn}`;
    });

    // Replace table functions
    js = js.replace(/table\.insert\s*\((.*?),\s*(.*?)\)/g, '$1.push($2)');
    js = js.replace(/table\.remove\s*\((.*?),\s*(.*?)\)/g, '$1.splice($2 - 1, 1)');
    js = js.replace(/table\.concat\s*\((.*?)\)/g, '$1.join("")');

    return js;
  };

  const execute = async (code: string): Promise<LuaRuntimeState> => {
    // Reset state
    state = {
      running: true,
      outputs: [],
      error: null,
      instances: [],
    };
    stopFlag = false;
    api = createRobloxAPI();

    addOutput('info', 'Starting script execution...');

    try {
      // Transpile Lua to JavaScript
      const jsCode = transpileLuaToJS(code);

      // Create execution context
      const context = {
        __print: (...args: any[]) => {
          addOutput('log', args.map(a => String(a)).join(' '));
        },
        __warn: (...args: any[]) => {
          addOutput('warn', args.map(a => String(a)).join(' '));
        },
        __wait: async (seconds: number = 0) => {
          if (stopFlag) throw new Error('Script stopped');
          await new Promise(resolve => setTimeout(resolve, (seconds || 0.03) * 1000));
          return seconds;
        },
        __game: api.game,
        __workspace: api.workspace,
        __Instance: {
          new: (className: string, parent?: Instance) => {
            const instance = api.Instance.new(className, parent);
            state.instances.push(instance);
            notifyInstanceUpdate();
            return instance;
          },
        },
        __Vector3: api.Vector3,
        __Color3: api.Color3,
        __CFrame: api.CFrame,
        __TweenInfo: api.TweenInfo,
        __Enum: api.Enum,
        __stringFormat: (format: string, ...args: any[]) => {
          let result = format;
          args.forEach((arg, i) => {
            result = result.replace(/%[dsf]/, String(arg));
          });
          return result;
        },
        console: {
          log: (...args: any[]) => addOutput('log', args.map(a => String(a)).join(' ')),
          warn: (...args: any[]) => addOutput('warn', args.map(a => String(a)).join(' ')),
          error: (...args: any[]) => addOutput('error', args.map(a => String(a)).join(' ')),
        },
        setTimeout,
        setInterval,
        clearTimeout,
        clearInterval,
        Math,
        String,
        Number,
        Array,
        Object,
        JSON,
        Date,
        Promise,
      };

      // Wrap the code in an async IIFE
      const wrappedCode = `
        (async () => {
          ${jsCode}
        })();
      `;

      // Create function with context
      const contextKeys = Object.keys(context);
      const contextValues = Object.values(context);

      // Use Function constructor to create isolated execution
      const fn = new Function(...contextKeys, wrappedCode);

      // Execute
      await fn(...contextValues);

      // Start RunService if we have connections
      api.runService.start();

      addOutput('info', 'Script executed successfully');
    } catch (error: any) {
      state.error = error.message || 'Unknown error';
      addOutput('error', `Error: ${state.error}`);
    }

    state.running = false;
    return state;
  };

  const stop = () => {
    stopFlag = true;
    state.running = false;
    api.runService.stop();
    addOutput('info', 'Script stopped');
  };

  return {
    execute,
    stop,
    getState: () => state,
    getInstances: () => state.instances,
    onOutput: (callback) => {
      outputCallbacks.push(callback);
    },
    onInstanceUpdate: (callback) => {
      instanceCallbacks.push(callback);
    },
  };
}

/**
 * Parse Lua code to extract instance creations for preview
 */
export function parseScriptForInstances(code: string): Array<{
  className: string;
  name?: string;
  properties: Record<string, any>;
}> {
  const instances: Array<{
    className: string;
    name?: string;
    properties: Record<string, any>;
  }> = [];

  // Match Instance.new calls
  const instancePattern = /Instance\.new\s*\(\s*["'](\w+)["']/g;
  let match;

  while ((match = instancePattern.exec(code)) !== null) {
    instances.push({
      className: match[1],
      properties: {},
    });
  }

  return instances;
}

/**
 * Validate Lua syntax (basic check)
 */
export function validateLuaSyntax(code: string): { valid: boolean; errors: string[] } {
  const errors: string[] = [];

  // Check for balanced keywords
  const keywords = {
    'function': 'end',
    'if': 'end',
    'for': 'end',
    'while': 'end',
    'repeat': 'until',
    'do': 'end',
  };

  // Remove strings and comments for analysis
  const cleaned = code
    .replace(/--\[\[[\s\S]*?\]\]/g, '')
    .replace(/--.*$/gm, '')
    .replace(/"[^"]*"/g, '""')
    .replace(/'[^']*'/g, "''");

  // Count keyword pairs
  for (const [start, end] of Object.entries(keywords)) {
    const startRegex = new RegExp(`\\b${start}\\b`, 'g');
    const endRegex = new RegExp(`\\b${end}\\b`, 'g');

    const startCount = (cleaned.match(startRegex) || []).length;
    const endCount = (cleaned.match(endRegex) || []).length;

    if (startCount !== endCount) {
      errors.push(`Unmatched '${start}' - found ${startCount} '${start}' but ${endCount} '${end}'`);
    }
  }

  // Check for common syntax errors
  if (/=\s*=\s*=/.test(cleaned)) {
    errors.push("Invalid operator '===' - use '==' for equality");
  }

  if (/!\s*=/.test(cleaned)) {
    errors.push("Invalid operator '!=' - use '~=' for not equal");
  }

  return {
    valid: errors.length === 0,
    errors,
  };
}
