/**
 * mx webapp command implementation.
 *
 * Scaffolds a complete browser web app package with:
 * - Domain actor (MatrixActor) for business logic
 * - Shell (MatrixActorHtmlElement) for browser UI
 * - Federation bootstrap (init.ts)
 * - Vite build config
 * - HTML template + CSS styles
 * - Tests and examples
 */

import * as fs from 'node:fs';
import * as path from 'node:path';
import {
  toKebabCase, toPascalCase, validateMatrixManifest, writeJsonFile,
  generatePackageJson, generateTsconfig,
} from '../utils/scaffold.js';

export interface IWebappCommandOptions {
  readonly cwd?: string;
  readonly outputDir?: string;
}

export interface IWebappCommandResult {
  readonly rootDir: string;
  readonly appClassName: string;
  readonly shellClassName: string;
  readonly tagName: string;
  readonly appName: string;
}

// ── Template generators ─────────────────────────────────────────────────

function appActorTemplate(className: string): string {
  return `import { MatrixActor } from '@open-matrix/core';

export class ${className} extends MatrixActor {
  static override accepts = {
    increment: {},
    decrement: {},
    getCount: {},
    reset: {},
  };

  static override emits = {
    countChanged: { count: 'number' },
  };

  private _count = 0;

  protected onIncrement(): void {
    this._count++;
    this.emit('countChanged', { count: this._count });
  }

  protected onDecrement(): void {
    this._count--;
    this.emit('countChanged', { count: this._count });
  }

  protected onGetCount(): { count: number } {
    return { count: this._count };
  }

  protected onReset(): void {
    this._count = 0;
    this.emit('countChanged', { count: this._count });
  }
}
`;
}

function shellTemplate(shellClassName: string, appClassName: string, tagName: string): string {
  return `import { MatrixActorHtmlElement } from '@open-matrix/core';
import { ${appClassName} } from '../${appClassName}.js';
import { appTemplate } from '../template/app.template.js';

export class ${shellClassName} extends MatrixActorHtmlElement {
  static override accepts = {
    ...${appClassName}.accepts,
  };

  static override emits = {
    ...${appClassName}.emits,
  };

  static override template = appTemplate;

  static getActorClass() {
    return ${appClassName};
  }

  async onBootstrap(): Promise<void> {
    const root = this.shadowRoot!;

    // Wire up button handlers
    root.getElementById('incrementBtn')?.addEventListener('click', () => {
      this.sendToSelf('increment', {});
    });
    root.getElementById('decrementBtn')?.addEventListener('click', () => {
      this.sendToSelf('decrement', {});
    });
    root.getElementById('resetBtn')?.addEventListener('click', () => {
      this.sendToSelf('reset', {});
    });
    root.getElementById('introspectBtn')?.addEventListener('click', () => {
      this.sendToSelf('$introspect', {});
    });

    // Request initial count
    this.sendToSelf('getCount', {});
  }

  onCountChanged(payload: { count: number }): void {
    const el = this.shadowRoot?.getElementById('countDisplay');
    if (el) el.textContent = String(payload.count);
  }
}

if (!customElements.get('${tagName}')) {
  customElements.define('${tagName}', ${shellClassName});
}
`;
}

function templateFileContent(appName: string): string {
  return `import { appStyles } from '../styles/app.styles.ts';

export const appTemplate = \`
  <div class="app-container">
    <header class="app-header">
      <h1>${appName}</h1>
      <div id="transportIndicator" class="transport-indicator">
        <span id="transportText">Connecting...</span>
      </div>
    </header>

    <main class="app-main">
      <div class="counter-card">
        <div class="counter-display">
          <span class="counter-label">Count:</span>
          <span id="countDisplay" class="counter-value">0</span>
        </div>
        <div class="counter-controls">
          <button id="decrementBtn" class="btn btn-secondary" type="button">-</button>
          <button id="incrementBtn" class="btn btn-primary" type="button">+</button>
          <button id="resetBtn" class="btn btn-outline" type="button">Reset</button>
        </div>
      </div>

      <div class="info-card">
        <p>This app demonstrates the Matrix Shell pattern:</p>
        <ul>
          <li><strong>Shell</strong> sends commands via <code>sendToSelf()</code></li>
          <li><strong>Actor</strong> processes commands and emits events</li>
          <li><strong>Shell</strong> reacts to events via <code>on&lt;Event&gt;()</code> handlers</li>
        </ul>
        <button id="introspectBtn" class="btn btn-outline" type="button">Introspect Actor</button>
      </div>
    </main>
  </div>
  <style>\${appStyles}</style>
\`;
`;
}

function stylesFileContent(): string {
  return `export const appStyles = \`
  :host {
    display: block;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    color: #e0e0e0;
    background: #1a1a2e;
    min-height: 100vh;
  }

  .app-container {
    max-width: 800px;
    margin: 0 auto;
    padding: 2rem;
  }

  .app-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 2rem;
    padding-bottom: 1rem;
    border-bottom: 1px solid #333;
  }

  .app-header h1 {
    margin: 0;
    font-size: 1.5rem;
    color: #00d4ff;
  }

  .transport-indicator {
    font-size: 0.8rem;
    padding: 4px 12px;
    border-radius: 12px;
    background: #2a2a4a;
  }

  .counter-card, .info-card {
    background: #16213e;
    border: 1px solid #2a2a4a;
    border-radius: 8px;
    padding: 2rem;
    margin-bottom: 1.5rem;
  }

  .counter-display {
    text-align: center;
    margin-bottom: 1.5rem;
  }

  .counter-label {
    font-size: 1rem;
    color: #888;
    margin-right: 0.5rem;
  }

  .counter-value {
    font-size: 3rem;
    font-weight: bold;
    color: #00d4ff;
    font-variant-numeric: tabular-nums;
  }

  .counter-controls {
    display: flex;
    justify-content: center;
    gap: 1rem;
  }

  .btn {
    padding: 8px 20px;
    border-radius: 6px;
    border: 1px solid #444;
    cursor: pointer;
    font-size: 1rem;
    transition: all 0.2s;
  }

  .btn-primary {
    background: #00d4ff;
    color: #000;
    border-color: #00d4ff;
  }
  .btn-primary:hover { background: #00b8e6; }

  .btn-secondary {
    background: #ff6b6b;
    color: #fff;
    border-color: #ff6b6b;
  }
  .btn-secondary:hover { background: #e55a5a; }

  .btn-outline {
    background: transparent;
    color: #aaa;
    border-color: #555;
  }
  .btn-outline:hover { border-color: #00d4ff; color: #00d4ff; }

  .info-card p { margin-top: 0; color: #999; }
  .info-card ul { color: #bbb; line-height: 1.8; }
  .info-card code {
    background: #2a2a4a;
    padding: 2px 6px;
    border-radius: 3px;
    font-size: 0.85em;
    color: #00d4ff;
  }
\`;
`;
}

function indexHtmlTemplate(tagName: string, appName: string): string {
  return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>${appName}</title>
  <style>
    body { margin: 0; background: #1a1a2e; }
    #loading {
      color: #888;
      font-family: sans-serif;
      text-align: center;
      padding: 4rem;
    }
  </style>
</head>
<body>
  <div id="loading">Loading ${appName}...</div>
  <${tagName} id="app" style="display:none"></${tagName}>
  <script type="module" src="./src/init.ts"></script>
</body>
</html>
`;
}

function initTemplate(tagName: string): string {
  return `/**
 * App initialization — sets up Matrix runtime and federation.
 */
import { MatrixRuntime, BrowserDomStrategy, TransportProxy } from '@open-matrix/core';
import {
  getBrowserRouter,
  createNatsTransport as createFederationNatsTransport,
} from '@open-matrix/federation';

// Register custom elements
import './index.ts';

const loadingEl = document.getElementById('loading')!;
const appEl = document.getElementById('app') as any;

async function init() {
  try {
    const urlParams = new URLSearchParams(window.location.search);
    const root = urlParams.get('root') || 'APP-ROOT';
    const backboneUrl = urlParams.get('backboneUrl') || 'nats://hub.example.com:4222';
    const allowLocalFallback = ['1', 'true'].includes(
      (urlParams.get('allowLocalFallback') || 'true').toLowerCase()
    );

    console.log('[App] Initializing...');
    loadingEl.textContent = 'Initializing Matrix runtime...';

    // Get browser router from federation package
    const { router, adapter, root: tabRoot } = getBrowserRouter({
      baseRoot: root,
    });

    console.log('[App] Tab root:', tabRoot.value);

    // Connect to NATS hub backbone for cross-root communication
    loadingEl.textContent = 'Connecting to federation backbone...';
    try {
      const natsModule = await import('nats');
      const nats = natsModule.default || natsModule;
      const connect = typeof nats === 'function' ? nats
        : typeof nats.connect === 'function' ? nats.connect
        : natsModule.connect;

      const clientId = \`app-\${Date.now()}-\${Math.random().toString(36).slice(2)}\`;
      const client = connect(backboneUrl, {
        clientId,
        clean: true,
        reconnectPeriod: 1000,
        connectTimeout: 10000,
      });

      await new Promise<void>((resolve, reject) => {
        const timeout = setTimeout(() => reject(new Error('Connection timeout')), 10000);
        client.on('connect', () => { clearTimeout(timeout); resolve(); });
        client.on('error', (err: Error) => { clearTimeout(timeout); reject(err); });
      });

      const backbone = createFederationNatsTransport(client as any, { jsonMode: false, qos: 0 });
      router.setBackbone(backbone);
      console.log('[App] Backbone connected');
    } catch (err: any) {
      if (!allowLocalFallback) throw err;
      console.warn('[App] Backbone connection failed, local-only mode:', err?.message);
    }

    // Create runtime
    const transportProxy = new TransportProxy(adapter as any);
    const runtime = new MatrixRuntime({
      transport: transportProxy,
      domStrategy: new BrowserDomStrategy(),
    });

    const context = runtime.getRootContext();
    const pageContext = context.derive('app-page');

    // Register services so shell components can find them
    const SESSION_INFO_KEY = Symbol.for('matrix.session.info');
    const FEDERATION_PEER_KEY = Symbol.for('matrix.federation.peer');
    context.setService(SESSION_INFO_KEY, {
      mode: 'router',
      root: tabRoot.value,
      hasBackbone: router.hasBackbone,
    });
    context.setService(FEDERATION_PEER_KEY, router);

    // Mount the app
    await appEl.mount(pageContext);

    // Update transport indicator
    const textEl = appEl.shadowRoot?.getElementById('transportText');
    if (textEl) {
      textEl.textContent = router.hasBackbone
        ? \`Federation (\${tabRoot.value})\`
        : \`Local-only (\${tabRoot.value})\`;
    }

    // Show app, hide loading
    loadingEl.style.display = 'none';
    appEl.style.display = 'block';

    console.log('[App] Ready!');
    console.log('[App] Root:', tabRoot.value);
    console.log('[App] Backbone:', router.hasBackbone ? 'connected' : 'local-only');
  } catch (error: any) {
    console.error('[App] Initialization failed:', error);
    loadingEl.outerHTML = \`
      <div style="color: #ff6b6b; font-family: sans-serif; text-align: center; padding: 4rem;">
        <strong>Failed to initialize</strong>
        <p>\${error.message}</p>
      </div>
    \`;
  }
}

init();
`;
}

function indexTsTemplate(shellClassName: string): string {
  return `export { ${shellClassName} } from './shell/${shellClassName}.js';
`;
}

function viteConfigTemplate(appName: string): string {
  return `import { defineConfig } from 'vite';
import * as path from 'node:path';

export default defineConfig({
  root: '.',
  base: '/apps/${appName}/',
  build: {
    outDir: 'dist',
    target: 'es2022',
    sourcemap: true,
  },
  resolve: {
    alias: {
      // In development, resolve to monorepo source.
      // For standalone use, install these as npm packages instead.
      '@open-matrix/core': path.resolve(__dirname, '../../src/index.ts'),
    },
  },
  optimizeDeps: {
    include: ['nats'],
  },
});
`;
}

function testTemplate(appClassName: string, shellClassName: string): string {
  return `import { describe, it } from 'node:test';
import { strict as assert } from 'node:assert';
import { ${appClassName} } from '../src/${appClassName}.ts';

describe('${appClassName}', () => {
  it('declares counter commands', () => {
    assert.ok('increment' in ${appClassName}.accepts);
    assert.ok('decrement' in ${appClassName}.accepts);
    assert.ok('getCount' in ${appClassName}.accepts);
    assert.ok('reset' in ${appClassName}.accepts);
  });

  it('declares countChanged event', () => {
    assert.ok('countChanged' in ${appClassName}.emits);
  });
});
`;
}

// ── Main command ─────────────────────────────────────────────────────────

export async function webappCommand(
  name: string,
  options: IWebappCommandOptions = {},
): Promise<IWebappCommandResult> {
  const normalizedName = toKebabCase(name);
  if (!normalizedName) {
    throw new Error('Webapp name must not be empty');
  }

  const baseName = toPascalCase(normalizedName);
  const appClassName = `${baseName}App`;
  const shellClassName = `${baseName}AppShell`;
  const tagName = `${normalizedName}-app`;
  const appName = baseName;
  const cwd = path.resolve(options.cwd ?? process.cwd());
  const rootDir = path.resolve(options.outputDir ?? path.join(cwd, normalizedName));

  // Create directory structure
  fs.mkdirSync(path.join(rootDir, 'src', 'shell'), { recursive: true });
  fs.mkdirSync(path.join(rootDir, 'src', 'template'), { recursive: true });
  fs.mkdirSync(path.join(rootDir, 'src', 'styles'), { recursive: true });
  fs.mkdirSync(path.join(rootDir, 'examples'), { recursive: true });
  fs.mkdirSync(path.join(rootDir, 'tests'), { recursive: true });

  // matrix.json
  const manifest = {
    name: `@open-matrix/${normalizedName}`,
    version: '0.1.0',
    description: `${appName} — a Matrix web application`,
    runtime: { language: 'typescript', entry: 'dist/index.js' },
    webapp: { distDir: 'dist', entry: 'index.html', appName: normalizedName },
    components: [],
    permissions: { fsPolicy: 'none' },
  };

  const validation = validateMatrixManifest(manifest);
  if (!validation.valid) {
    throw new Error(`Generated matrix.json failed validation: ${validation.errors.join('; ')}`);
  }

  writeJsonFile(path.join(rootDir, 'matrix.json'), manifest);
  writeJsonFile(path.join(rootDir, 'package.json'), generatePackageJson(normalizedName, { webapp: true }));
  writeJsonFile(path.join(rootDir, 'tsconfig.json'), generateTsconfig({ webapp: true }));

  // Vite config
  fs.writeFileSync(path.join(rootDir, 'vite.config.ts'), viteConfigTemplate(normalizedName), 'utf8');

  // index.html (entry point)
  fs.writeFileSync(path.join(rootDir, 'index.html'), indexHtmlTemplate(tagName, appName), 'utf8');

  // Source files
  fs.writeFileSync(path.join(rootDir, 'src', 'index.ts'), indexTsTemplate(shellClassName), 'utf8');
  fs.writeFileSync(path.join(rootDir, 'src', 'init.ts'), initTemplate(tagName), 'utf8');
  fs.writeFileSync(path.join(rootDir, 'src', `${appClassName}.ts`), appActorTemplate(appClassName), 'utf8');
  fs.writeFileSync(path.join(rootDir, 'src', 'shell', `${shellClassName}.ts`), shellTemplate(shellClassName, appClassName, tagName), 'utf8');
  fs.writeFileSync(path.join(rootDir, 'src', 'template', 'app.template.ts'), templateFileContent(appName), 'utf8');
  fs.writeFileSync(path.join(rootDir, 'src', 'styles', 'app.styles.ts'), stylesFileContent(), 'utf8');

  // Example
  writeJsonFile(path.join(rootDir, 'examples', 'introspect.mxq'), {
    label: 'Introspect App',
    operation: '$introspect',
    payload: {},
    description: `Introspect the ${appName} actor`,
  });

  // Test
  fs.writeFileSync(path.join(rootDir, 'tests', `${normalizedName}.spec.ts`), testTemplate(appClassName, shellClassName), 'utf8');

  console.log(`Created webapp scaffold at ${rootDir}`);
  console.log(`  Tag: <${tagName}>`);
  console.log(`  Actor: ${appClassName}`);
  console.log(`  Shell: ${shellClassName}`);
  return { rootDir, appClassName, shellClassName, tagName, appName: normalizedName };
}
