// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import m from 'mithril';
import {assetSrc} from '../../base/assets';
import type {
  SqlModules,
  SqlModule,
  SqlTable,
  SqlColumn,
  SqlFunction,
  SqlTableFunction,
  SqlMacro,
} from './sql_modules';
import type {TableColumn} from '../../components/widgets/sql/table/table_column';
import type {SqlTableDefinition} from '../../components/widgets/sql/table/table_description';
import {
  parsePerfettoSqlTypeFromString,
  type PerfettoSqlType,
} from '../../trace_processor/perfetto_sql_type';
import {unwrapResult} from '../../base/result';

// Lightweight SqlModules implementation for BigTrace that loads stdlib_docs.json
// without requiring a Trace object.

class SimpleSqlColumn implements SqlColumn {
  readonly name: string;
  readonly type: PerfettoSqlType;
  readonly description: string;

  constructor(
    name: string,
    typeStr: string,
    description: string,
    tableName: string,
  ) {
    this.name = name;
    this.type = unwrapResult(
      parsePerfettoSqlTypeFromString({
        type: typeStr,
        table: tableName,
        column: name,
      }),
    );
    this.description = description;
  }
}

class SimpleSqlTable implements SqlTable {
  readonly name: string;
  readonly includeKey?: string;
  readonly description: string;
  readonly type: string;
  readonly importance?: 'core' | 'high' | 'mid' | 'low';
  readonly columns: SqlColumn[];

  constructor(
    name: string,
    description: string,
    type: string,
    columns: SqlColumn[],
    includeKey?: string,
    importance?: 'core' | 'high' | 'mid' | 'low',
  ) {
    this.name = name;
    this.description = description;
    this.type = type;
    this.columns = columns;
    this.includeKey = includeKey;
    this.importance = importance;
  }

  getTableColumns(): TableColumn[] {
    return [];
  }
}

class SimpleSqlModule implements SqlModule {
  readonly includeKey: string;
  readonly tags: string[];
  readonly tables: SqlTable[];
  readonly functions: SqlFunction[];
  readonly tableFunctions: SqlTableFunction[];
  readonly macros: SqlMacro[];

  constructor(includeKey: string, tags: string[], tables: SqlTable[]) {
    this.includeKey = includeKey;
    this.tags = tags;
    this.tables = tables;
    this.functions = [];
    this.tableFunctions = [];
    this.macros = [];
  }

  getTable(tableName: string): SqlTable | undefined {
    return this.tables.find((t) => t.name === tableName);
  }

  getSqlTableDefinition(tableName: string): SqlTableDefinition | undefined {
    const table = this.getTable(tableName);
    if (!table) return undefined;
    return {
      imports: [this.includeKey],
      name: table.name,
      columns: table.columns.map((col) => ({
        column: col.name,
        type: col.type,
      })),
    };
  }
}

// Shape of the stdlib_docs.json entries (parsed without Zod to avoid CSP).
interface StdlibColumn {
  name: string;
  type: string;
  desc: string;
}

interface StdlibDataObject {
  name: string;
  desc: string;
  type: string;
  cols: StdlibColumn[];
  importance?: 'core' | 'high' | 'mid' | 'low';
}

interface StdlibModule {
  module_name: string;
  tags?: string[];
  data_objects: StdlibDataObject[];
}

interface StdlibPackage {
  modules: StdlibModule[];
}

class SimpleSqlModules implements SqlModules {
  private modules: SimpleSqlModule[];

  constructor(docs: unknown[]) {
    this.modules = [];
    for (const pkg of docs as StdlibPackage[]) {
      for (const mod of pkg.modules) {
        const includeKey: string = mod.module_name;
        const neededInclude = includeKey.startsWith('prelude')
          ? undefined
          : includeKey;
        const tables = mod.data_objects.map((obj) => {
          const columns = obj.cols.map(
            (col) =>
              new SimpleSqlColumn(col.name, col.type, col.desc, obj.name),
          );
          return new SimpleSqlTable(
            obj.name,
            obj.desc,
            obj.type,
            columns,
            neededInclude,
            obj.importance ?? undefined,
          );
        });
        this.modules.push(
          new SimpleSqlModule(includeKey, mod.tags ?? [], tables),
        );
      }
    }
  }

  listTables(): SqlTable[] {
    return this.modules.flatMap((mod) => mod.tables);
  }

  listModules(): SqlModule[] {
    return this.modules;
  }

  listTablesNames(): string[] {
    return this.listTables().map((t) => t.name);
  }

  getTable(tableName: string): SqlTable | undefined {
    for (const mod of this.modules) {
      const t = mod.getTable(tableName);
      if (t) return t;
    }
    return undefined;
  }

  getModuleForTable(tableName: string): SqlModule | undefined {
    for (const mod of this.modules) {
      if (mod.tables.some((t) => t.name === tableName)) {
        return mod;
      }
    }
    return undefined;
  }

  isModuleDisabled(_moduleName: string): boolean {
    return false;
  }

  getDisabledModules(): ReadonlySet<string> {
    return new Set();
  }

  ensureInitialized(): Promise<void> {
    return Promise.resolve();
  }
}

// Singleton that lazily loads the stdlib docs.
class SqlTablesLoader {
  private sqlModules: SqlModules | undefined;
  private loading = false;
  private error: string | undefined;

  get modules(): SqlModules | undefined {
    return this.sqlModules;
  }

  get isLoading(): boolean {
    return this.loading;
  }

  get loadError(): string | undefined {
    return this.error;
  }

  async load(): Promise<void> {
    if (this.sqlModules || this.loading) return;
    this.loading = true;
    this.error = undefined;
    try {
      // stdlib_docs.json lives in the parent Perfetto dist directory.
      const resp = await fetch(assetSrc('../stdlib_docs.json'));
      if (!resp.ok) {
        throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
      }
      const json = await resp.json();
      // Avoid Zod .parse() here — it uses new Function() which violates CSP.
      this.sqlModules = new SimpleSqlModules(json);
    } catch (e) {
      this.error = e instanceof Error ? e.message : String(e);
    } finally {
      this.loading = false;
      m.redraw();
    }
  }
}

export const sqlTablesLoader = new SqlTablesLoader();
