// Copyright (C) 2025 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 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 type {Engine} from '../../../trace_processor/engine';
import {NUM_NULL, UNKNOWN} from '../../../trace_processor/query_result';
import {Button} from '../../../widgets/button';
import {Section} from '../../../widgets/section';
import {Icon} from '../../../widgets/icon';
import {Tooltip} from '../../../widgets/tooltip';
import {statsSpec, type StatsSectionRow} from '../utils';

// Trace metadata row spec and type
const traceMetadataRowSpec = {
  name: UNKNOWN,
  value: UNKNOWN,
  machineId: NUM_NULL,
  traceId: NUM_NULL,
};
type TraceMetadataRow = typeof traceMetadataRowSpec;

export interface StatsData {
  traceMetadata: TraceMetadataRow[];
  allStats: StatsSectionRow[];
  isMultiTrace: boolean;
  isMultiMachine: boolean;
}

export async function loadStatsData(engine: Engine): Promise<StatsData> {
  // Load all stats
  const allStatsResult = await engine.query(`
    select
      name,
      value,
      cast(ifnull(idx, '') as text) as idx,
      description,
      severity,
      source,
      machine_id as machineId,
      trace_id as traceId
    from stats
    order by trace_id, machine_id, name, idx
  `);
  const allStats: StatsSectionRow[] = [];
  for (const iter = allStatsResult.iter(statsSpec); iter.valid(); iter.next()) {
    allStats.push({
      name: iter.name,
      value: iter.value,
      description: iter.description,
      idx: iter.idx,
      severity: iter.severity,
      source: iter.source,
      machineId: iter.machineId,
      traceId: iter.traceId,
    });
  }

  // Load trace metadata
  const traceMetadataResult = await engine.query(`
    with metadata_with_priorities as (
      select
        name,
        ifnull(str_value, cast(int_value as text)) as value,
        machine_id,
        trace_id,
        name in (
          "trace_size_bytes",
          "cr-os-arch",
          "cr-os-name",
          "cr-os-version",
          "cr-physical-memory",
          "cr-product-version",
          "cr-hardware-class"
        ) as priority
      from metadata
    )
    select
      name,
      value,
      machine_id as machineId,
      trace_id as traceId
    from metadata_with_priorities
    order by
      trace_id,
      machine_id,
      priority desc,
      name
  `);

  // Load trace and machine info
  const traceIds = new Set<number>();
  const machineIds = new Set<number>();

  const traceMetadata: TraceMetadataRow[] = [];
  for (
    const iter = traceMetadataResult.iter(traceMetadataRowSpec);
    iter.valid();
    iter.next()
  ) {
    if (iter.machineId !== null) {
      machineIds.add(iter.machineId);
    }
    if (iter.traceId !== null) {
      traceIds.add(iter.traceId);
    }
    traceMetadata.push({
      name: iter.name,
      value: iter.value,
      machineId: iter.machineId,
      traceId: iter.traceId,
    });
  }

  // Ensure every machine_id / trace_id observed in the stats rows is
  // covered when deciding whether to show the per-machine/per-trace
  // columns — the metadata table alone may not have seen all of them.
  for (const stat of allStats) {
    if (stat.machineId !== null) {
      machineIds.add(stat.machineId);
    }
    if (stat.traceId !== null) {
      traceIds.add(stat.traceId);
    }
  }

  return {
    traceMetadata,
    allStats,
    isMultiTrace: traceIds.size > 1,
    isMultiMachine: machineIds.size > 1,
  };
}

export interface StatsTabAttrs {
  data: StatsData;
}

export class StatsTab implements m.ClassComponent<StatsTabAttrs> {
  view({attrs}: m.CVnode<StatsTabAttrs>) {
    return m(
      '.pf-trace-info-page__tab-content',
      m(
        Section,
        {
          title: 'Trace Metadata',
          subtitle: 'All metadata key-value pairs recorded in the trace',
        },
        m(TraceMetadata, {
          data: attrs.data.traceMetadata,
          isMultiTrace: attrs.data.isMultiTrace,
          isMultiMachine: attrs.data.isMultiMachine,
        }),
      ),
      m(
        Section,
        {
          title: 'Statistics',
          subtitle:
            'Complete dump of all trace statistics including errors, data losses, and debugging info',
        },
        m(StatsSection, {
          data: attrs.data.allStats,
          isMultiTrace: attrs.data.isMultiTrace,
          isMultiMachine: attrs.data.isMultiMachine,
        }),
      ),
    );
  }
}

// Trace Metadata Section
interface TraceMetadataAttrs {
  data: TraceMetadataRow[];
  isMultiTrace: boolean;
  isMultiMachine: boolean;
}

class TraceMetadata implements m.ClassComponent<TraceMetadataAttrs> {
  view({attrs}: m.CVnode<TraceMetadataAttrs>) {
    const tableRows = attrs.data.map((row) => {
      const columns = [];
      if (attrs.isMultiTrace) {
        columns.push(
          m(
            'td.pf-trace-info-page__stats-table-cell',
            row.traceId === null ? '-' : row.traceId,
          ),
        );
      }
      if (attrs.isMultiMachine) {
        columns.push(
          m(
            'td.pf-trace-info-page__stats-table-cell',
            row.machineId === null ? '-' : row.machineId,
          ),
        );
      }
      columns.push(
        m(
          'td.pf-trace-info-page__stats-table-cell.pf-trace-info-page__stats-table-cell--name',
          `${row.name}`,
        ),
      );
      columns.push(
        m('td.pf-trace-info-page__stats-table-cell', `${row.value}`),
      );
      return m('tr.pf-trace-info-page__stats-table-row', columns);
    });

    const headerCols = [];
    if (attrs.isMultiTrace) {
      headerCols.push(
        m('td.pf-trace-info-page__stats-table-head-cell', 'Trace'),
      );
    }
    if (attrs.isMultiMachine) {
      headerCols.push(
        m('td.pf-trace-info-page__stats-table-head-cell', 'Machine'),
      );
    }
    headerCols.push(m('td.pf-trace-info-page__stats-table-head-cell', 'Name'));
    headerCols.push(m('td.pf-trace-info-page__stats-table-head-cell', 'Value'));

    return m(
      'section.pf-trace-info-page__stats-section',
      m(
        'table.pf-trace-info-page__stats-table',
        m('thead', m('tr', headerCols)),
        m('tbody', tableRows),
      ),
    );
  }
}

// Stats Section
interface StatsSectionAttrs {
  data: StatsSectionRow[];
  isMultiTrace: boolean;
  isMultiMachine: boolean;
}

class StatsSection implements m.ClassComponent<StatsSectionAttrs> {
  private hideZeroValues = true;

  view({attrs}: m.CVnode<StatsSectionAttrs>) {
    const data = attrs.data;
    if (data === undefined || data.length === 0) {
      return m('');
    }

    const filtered = this.hideZeroValues
      ? data.filter((row) => row.value !== 0 && row.value !== null)
      : data;

    const tableRows = filtered.map((row) => {
      const help = [];
      if (Boolean(row.description)) {
        help.push(
          m(
            Tooltip,
            {
              trigger: m(Icon, {
                icon: 'help_outline',
                className: 'pf-trace-info-page__help-icon',
              }),
            },
            `${row.description}`,
          ),
        );
      }
      const idx = row.idx !== '' ? `[${row.idx}]` : '';
      const cells = [];
      if (attrs.isMultiTrace) {
        cells.push(
          m(
            'td.pf-trace-info-page__stats-table-cell',
            row.traceId === null ? '-' : row.traceId,
          ),
        );
      }
      if (attrs.isMultiMachine) {
        cells.push(
          m(
            'td.pf-trace-info-page__stats-table-cell',
            row.machineId === null ? '-' : row.machineId,
          ),
        );
      }
      cells.push(
        m(
          'td.pf-trace-info-page__stats-table-cell.pf-trace-info-page__stats-table-cell--name',
          `${row.name}${idx}`,
          help,
        ),
        m('td.pf-trace-info-page__stats-table-cell', `${row.value}`),
        m(
          'td.pf-trace-info-page__stats-table-cell',
          `${row.severity} (${row.source})`,
        ),
      );
      return m('tr.pf-trace-info-page__stats-table-row', cells);
    });

    const headerCols = [];
    if (attrs.isMultiTrace) {
      headerCols.push(
        m('td.pf-trace-info-page__stats-table-head-cell', 'Trace'),
      );
    }
    if (attrs.isMultiMachine) {
      headerCols.push(
        m('td.pf-trace-info-page__stats-table-head-cell', 'Machine'),
      );
    }
    headerCols.push(
      m('td.pf-trace-info-page__stats-table-head-cell', 'Name'),
      m('td.pf-trace-info-page__stats-table-head-cell', 'Value'),
      m('td.pf-trace-info-page__stats-table-head-cell', 'Type'),
    );

    return m(
      'section.pf-trace-info-page__stats-section',
      m(Button, {
        label: this.hideZeroValues ? 'Show zero values' : 'Hide zero values',
        icon: this.hideZeroValues ? 'visibility' : 'visibility_off',
        onclick: () => {
          this.hideZeroValues = !this.hideZeroValues;
        },
      }),
      m(
        'table.pf-trace-info-page__stats-table',
        m('thead', m('tr', headerCols)),
        m('tbody', tableRows),
      ),
    );
  }
}
