/**
 * Operation-view emitters for ReactAppStarter
 *
 * Source strings for the three `src/lib/*.tsx` files that back the
 * auto-generated operation views:
 *
 *   - FieldInput.tsx (+ EntitySelect) — type-aware parameter inputs.
 *   - OperationExecutor.tsx — param form + Execute button + result.
 *   - OperationResultView.tsx — formatted result / error pane.
 *
 * Ported from app-demo (frontend-react/src/lib/view-components/
 * FieldInput.tsx, components/runtime/OperationExecutor.tsx,
 * components/runtime/OperationResultView.tsx). Adaptations:
 *
 *   - EntitySelect fetch: POST /controllers/{M}Controller/list →
 *     GET /api/{resource} (static REST convention).
 *   - Response-shape: app-demo wraps entities in data.data?.entities
 *     or data.entities; static returns a flat array.
 *   - Entity id extraction: entity.data?.id || entity.id →
 *     entity.id (no interim IDs in static).
 *   - OperationExecutor controller URL:
 *     apiExecuteOperation(controllerName, op, params) →
 *     POST /api/{resource}/{op} where resource = plural lowercase
 *     of (controllerName minus "Controller" suffix).
 *
 * The three files are emitted by views-generator.ts into src/lib/
 * alongside entity-display.ts. They have no @specverse/* runtime
 * dependency; lucide-react is added to the generated package.json.
 */

export function emitFieldInput(): string {
  return `/**
 * FieldInput — type-aware input component used by OperationExecutor.
 *
 * Inlined into this project by @specverse/realize (ReactAppStarter).
 * Edit freely. Ported from app-demo's FieldInput.
 *
 * Renders the right input for a parameter type:
 *   - UUID ending in Id → entity dropdown via EntitySelect
 *   - Boolean → checkbox (or select in compact mode)
 *   - Integer/Number/Decimal → number input
 *   - DateTime/Date → native picker
 *   - Email → email input
 *   - Text (multiline) → textarea
 *   - default → text input
 */
import { useState, useEffect } from 'react';
import { getEntityDisplayName } from './entity-display';

const API_BASE = import.meta.env.VITE_API_BASE_URL || '';

function pluralize(s: string): string {
  if (/[^aeiou]y$/i.test(s)) return s.slice(0, -1) + 'ies';
  if (/(s|x|z|ch|sh)$/i.test(s)) return s + 'es';
  return s + 's';
}

function humanizeFieldName(name: string): string {
  return name
    .replace(/([A-Z])/g, ' $1')
    .replace(/^./, c => c.toUpperCase())
    .trim();
}

interface FieldInputProps {
  name: string;
  typeStr: string;
  value: any;
  onChange: (value: any) => void;
  required?: boolean;
  disabled?: boolean;
  /** Compact mode for inline use (smaller padding, no label). */
  compact?: boolean;
}

const inputClass = (compact: boolean) => compact
  ? 'w-full px-2 py-1 text-xs bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded focus:outline-none focus:ring-1 focus:ring-blue-500'
  : 'w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 dark:disabled:bg-gray-800 disabled:cursor-not-allowed';

export function FieldInput({ name, typeStr, value, onChange, required, disabled, compact = false }: FieldInputProps) {
  const type = typeStr.toLowerCase();

  if (type.includes('bool')) {
    return compact ? (
      <select
        value={value ?? ''}
        onChange={e => onChange(e.target.value === 'true')}
        disabled={disabled}
        className={inputClass(compact)}
      >
        <option value="">Select...</option>
        <option value="true">true</option>
        <option value="false">false</option>
      </select>
    ) : (
      <div className="flex items-center pt-2">
        <input
          type="checkbox"
          checked={!!value}
          onChange={e => onChange(e.target.checked)}
          disabled={disabled}
          className="h-4 w-4 text-blue-600 border-gray-300 dark:border-gray-600 rounded focus:ring-blue-500"
        />
        <span className="ml-2 text-sm text-gray-600 dark:text-gray-300">
          {value ? 'Yes' : 'No'}
        </span>
      </div>
    );
  }

  if (type.includes('int') || type.includes('number') || type.includes('decimal')) {
    return (
      <input
        type="number"
        step={type.includes('decimal') ? '0.01' : '1'}
        value={value ?? ''}
        onChange={e => onChange(type.includes('int') ? parseInt(e.target.value) || 0 : parseFloat(e.target.value) || 0)}
        disabled={disabled}
        required={required}
        className={inputClass(compact)}
      />
    );
  }

  if (type.includes('datetime')) {
    return (
      <input
        type="datetime-local"
        value={value ? new Date(value).toISOString().slice(0, 16) : ''}
        onChange={e => onChange(e.target.value ? new Date(e.target.value).toISOString() : '')}
        disabled={disabled}
        required={required}
        className={inputClass(compact)}
      />
    );
  }

  if (type.includes('date')) {
    return (
      <input
        type="date"
        value={value ? new Date(value).toISOString().slice(0, 10) : ''}
        onChange={e => onChange(e.target.value ? new Date(e.target.value).toISOString() : '')}
        disabled={disabled}
        required={required}
        className={inputClass(compact)}
      />
    );
  }

  if (type.includes('email')) {
    return (
      <input
        type="email"
        value={value ?? ''}
        onChange={e => onChange(e.target.value)}
        disabled={disabled}
        required={required}
        placeholder={humanizeFieldName(name)}
        className={inputClass(compact)}
      />
    );
  }

  if (type === 'text') {
    return (
      <textarea
        value={value ?? ''}
        onChange={e => onChange(e.target.value)}
        disabled={disabled}
        required={required}
        rows={compact ? 2 : 3}
        placeholder={humanizeFieldName(name)}
        className={\`\${inputClass(compact)} resize-none\`}
      />
    );
  }

  return (
    <input
      type="text"
      value={value ?? ''}
      onChange={e => onChange(e.target.value)}
      disabled={disabled}
      required={required}
      placeholder={\`\${humanizeFieldName(name)}\${typeStr ? \` (\${typeStr})\` : ''}\`}
      className={inputClass(compact)}
    />
  );
}

/**
 * EntitySelect — dropdown of existing entities, for UUID parameters
 * that reference a model. Fetches \`GET /api/{resource}\` (static REST
 * convention) and renders each entity by its display name.
 */
interface EntitySelectProps {
  modelName: string;
  value: string;
  onChange: (value: string) => void;
  compact?: boolean;
  /** Filter entities by a foreign key value, e.g. { categoryId: 'abc-123' }.
   *  Used for cascading pickers where a second param depends on the first. */
  filterBy?: Record<string, string>;
}

export function EntitySelect({ modelName, value, onChange, compact = false, filterBy }: EntitySelectProps) {
  const [allEntities, setAllEntities] = useState<any[]>([]);
  const resource = pluralize(modelName).toLowerCase();

  useEffect(() => {
    fetch(\`\${API_BASE}/api/\${resource}\`)
      .then(r => r.json())
      .then(data => setAllEntities(Array.isArray(data) ? data : []))
      .catch(() => setAllEntities([]));
  }, [resource]);

  const entities = filterBy
    ? allEntities.filter(e => {
        for (const [fk, fkValue] of Object.entries(filterBy)) {
          if (!fkValue) continue;
          if (!(fk in e)) continue;
          if (e[fk] !== fkValue) return false;
        }
        return true;
      })
    : allEntities;

  return (
    <select
      value={value || ''}
      onChange={e => onChange(e.target.value)}
      className={inputClass(compact)}
    >
      <option value="">
        Select {modelName}...{filterBy && Object.values(filterBy).some(v => v) ? \` (\${entities.length} matching)\` : ''}
      </option>
      {entities.map(entity => {
        const id = entity.id;
        return (
          <option key={id} value={id}>
            {getEntityDisplayName(entity)}{id ? \` (\${String(id).slice(0, 8)}...)\` : ''}
          </option>
        );
      })}
    </select>
  );
}
`;
}

export function emitOperationExecutor(): string {
  return `/**
 * OperationExecutor — run a controller command or service operation
 * against the backend. Renders a parameter form, an Execute button,
 * and the result/error beneath.
 *
 * Inlined into this project by @specverse/realize (ReactAppStarter).
 * Edit freely. Ported from app-demo's OperationExecutor.
 *
 * For source='service' ops: POST /api/services/{serviceName}/{operationName}
 * For source='controller' ops: POST /api/{resource}/{operationName}, where
 *   resource is the pluralized-lowercase form of the controller's managed
 *   model (ControllerName minus 'Controller' suffix).
 */
import { useState, useMemo } from 'react';
import { FieldInput, EntitySelect } from './FieldInput';
import { OperationResultView } from './OperationResultView';
import { Play } from 'lucide-react';

const API_BASE = import.meta.env.VITE_API_BASE_URL || '';

function pluralize(s: string): string {
  if (/[^aeiou]y$/i.test(s)) return s.slice(0, -1) + 'ies';
  if (/(s|x|z|ch|sh)$/i.test(s)) return s + 'es';
  return s + 's';
}

interface OperationExecutorProps {
  /** The service (source='service') or controller (source='controller') name. */
  serviceName: string;
  operationName: string;
  /** Parameter declarations from the spec. Supports both
   *  \`{ name: { type: 'String' } }\` and \`{ name: 'String' }\` shapes. */
  parameters?: Record<string, any>;
  /** Optional; currently unused but kept for forward compat with
   *  app-demo where requires gates the run. */
  requires?: string[];
  source?: 'service' | 'controller';
}

export function OperationExecutor({
  serviceName,
  operationName,
  parameters,
  requires: _requires,
  source = 'service',
}: OperationExecutorProps) {
  const [params, setParams] = useState<Record<string, any>>({});
  const [loading, setLoading] = useState(false);
  const [result, setResult] = useState<any>(null);
  const [error, setError] = useState<string | null>(null);

  const paramDefs = useMemo(() => {
    if (!parameters) return [];
    const entries: Array<[string, any]> = Array.isArray(parameters)
      ? parameters.map((p: any) => [p.name, p])
      : Object.entries(parameters);
    if (entries.length === 0) return [];

    return entries.map(([name, def]) => {
      const typeStr = typeof def === 'string' ? def : def?.type || '';
      const isUuid = typeStr.toLowerCase().includes('uuid');
      let refModel: string | null = null;
      if (isUuid && name.endsWith('Id')) {
        const base = name.slice(0, -2);
        refModel = base.charAt(0).toUpperCase() + base.slice(1);
      }
      return { name, type: typeStr, isUuid, refModel };
    });
  }, [parameters]);

  const execute = async () => {
    setLoading(true);
    setError(null);
    setResult(null);
    try {
      let url: string;
      if (source === 'controller') {
        // ControllerName → resource-plural-lowercase (PostController → posts)
        const modelName = serviceName.replace(/Controller$/, '');
        const resource = pluralize(modelName).toLowerCase();
        url = \`\${API_BASE}/api/\${resource}/\${operationName}\`;
      } else {
        url = \`\${API_BASE}/api/services/\${serviceName}/\${operationName}\`;
      }
      const res = await fetch(url, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(params),
      });
      const data = await res.json();
      setResult(data);
      if (!res.ok) {
        setError(data?.message || data?.error || \`\${res.status} \${res.statusText}\`);
      } else if (data && data.success === false) {
        setError(data.error?.message || 'Operation failed');
      }
    } catch (err: any) {
      setError(err?.message ?? String(err));
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="mt-3 pt-3 border-t border-gray-200 dark:border-gray-600 space-y-2">
      {paramDefs.length > 0 ? (
        paramDefs.map(param => {
          // Cascading filter: if this param's model has a belongsTo to another
          // already-filled param's model, filter by it.
          let filterBy: Record<string, string> | undefined;
          if (param.refModel) {
            for (const other of paramDefs) {
              if (other === param || !other.refModel) continue;
              const fk = other.refModel.charAt(0).toLowerCase() + other.refModel.slice(1) + 'Id';
              if (params[other.name]) {
                filterBy = { ...filterBy, [fk]: params[other.name] };
              }
            }
          }
          return (
            <div key={param.name} className="flex items-center gap-2">
              <label
                className="text-xs text-gray-500 dark:text-gray-400 w-24 flex-shrink-0 truncate"
                title={param.name}
              >
                {param.name}
              </label>
              {param.refModel ? (
                <EntitySelect
                  modelName={param.refModel}
                  value={params[param.name] || ''}
                  onChange={v => setParams(prev => ({ ...prev, [param.name]: v }))}
                  filterBy={filterBy}
                  compact
                />
              ) : (
                <FieldInput
                  name={param.name}
                  typeStr={param.type}
                  value={params[param.name]}
                  onChange={v => setParams(prev => ({ ...prev, [param.name]: v }))}
                  compact
                />
              )}
            </div>
          );
        })
      ) : (
        <p className="text-xs text-gray-400 italic">No parameters defined</p>
      )}

      <button
        onClick={execute}
        disabled={loading}
        className="w-full px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white text-xs font-medium rounded flex items-center justify-center gap-1 disabled:opacity-50"
      >
        <Play className="w-3 h-3" />
        {loading ? 'Executing...' : \`Execute \${operationName}\`}
      </button>

      {error && !result && (
        <pre className="p-2 text-xs text-red-400 bg-red-50 dark:bg-red-900/20 rounded overflow-x-auto">
          {error}
        </pre>
      )}
      {result && <OperationResultView result={result} />}
    </div>
  );
}
`;
}

export function emitOperationResultView(): string {
  return `/**
 * OperationResultView — formatted display of an operation's result.
 *
 * Inlined into this project by @specverse/realize (ReactAppStarter).
 * Edit freely. Ported from app-demo's OperationResultView.
 *
 * Handles three shapes:
 *   - Success with data → JSON pretty-print
 *   - Success without data → "Operation succeeded" banner
 *   - Error { success: false, error: ... } → error banner + details
 *   - Unexpected → raw JSON
 */
import { CheckCircle, XCircle } from 'lucide-react';

interface OperationResultViewProps {
  result: any;
}

function JsonDisplay({ value }: { value: any }) {
  return (
    <pre className="p-3 text-xs bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded overflow-x-auto text-gray-900 dark:text-gray-100">
      {JSON.stringify(value, null, 2)}
    </pre>
  );
}

export function OperationResultView({ result }: OperationResultViewProps) {
  // Explicit error envelope: { success: false, error: ... }
  if (result && typeof result === 'object' && result.success === false) {
    const errMsg =
      (typeof result.error === 'object' ? result.error?.message : result.error) ||
      'Operation failed';
    return (
      <div className="space-y-2">
        <div className="flex items-start gap-2 p-2 rounded bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300 text-xs">
          <XCircle className="w-4 h-4 flex-shrink-0 mt-0.5" />
          <div>
            <div className="font-medium">Operation failed</div>
            <div className="mt-1">{errMsg}</div>
          </div>
        </div>
        {result.error && typeof result.error === 'object' && <JsonDisplay value={result.error} />}
      </div>
    );
  }

  // Explicit success envelope: { success: true, data?: ... }
  if (result && typeof result === 'object' && result.success === true) {
    return (
      <div className="space-y-2">
        <div className="flex items-center gap-2 p-2 rounded bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300 text-xs">
          <CheckCircle className="w-4 h-4 flex-shrink-0" />
          <span className="font-medium">Operation succeeded</span>
        </div>
        {result.data !== undefined && <JsonDisplay value={result.data} />}
      </div>
    );
  }

  // No envelope — just show whatever came back (most REST endpoints).
  return (
    <div className="space-y-2">
      <div className="flex items-center gap-2 p-2 rounded bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300 text-xs">
        <CheckCircle className="w-4 h-4 flex-shrink-0" />
        <span className="font-medium">Operation completed</span>
      </div>
      {result !== undefined && result !== null && <JsonDisplay value={result} />}
    </div>
  );
}
`;
}
