// 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 type {SqlValue, Row} from '../../../trace_processor/query_result';
import {DataGrid} from '../../../components/widgets/datagrid/datagrid';
import type {SchemaRegistry} from '../../../components/widgets/datagrid/datagrid_schema';
import type {OverviewData} from '../types';
import {fmtSize} from '../format';
import type {NavState} from '../nav_state';
import {type NavFn, sizeRenderer} from '../components';
import type {HeapDump} from '../queries';
import {Callout} from '../../../widgets/callout';
import {Button} from '../../../widgets/button';

const HEAP_SCHEMA: SchemaRegistry = {
  query: {
    heap: {
      title: 'Heap',
      columnType: 'text',
    },
    java_size: {
      title: 'Java Size',
      columnType: 'quantitative',
      cellRenderer: sizeRenderer,
    },
    native_size: {
      title: 'Native Size',
      columnType: 'quantitative',
      cellRenderer: sizeRenderer,
    },
    total_size: {
      title: 'Total Size',
      columnType: 'quantitative',
      cellRenderer: sizeRenderer,
    },
  },
};

const INFO_SCHEMA: SchemaRegistry = {
  query: {
    property: {
      title: 'Property',
      columnType: 'text',
    },
    value: {
      title: 'Value',
      columnType: 'text',
    },
  },
};

function makeDuplicateBitmapSchema(navigate: NavFn): SchemaRegistry {
  return {
    query: {
      dimensions: {
        title: 'Dimensions',
        columnType: 'text',
      },
      copies: {
        title: 'Copies',
        columnType: 'quantitative',
        cellRenderer: (value: SqlValue, row) =>
          m(
            'button',
            {
              class: 'pf-hde-link',
              onclick: () =>
                navigate('bitmaps', {
                  filterKey: String(row.groupKey ?? ''),
                }),
            },
            String(value),
          ),
      },
      total_bytes: {
        title: 'Total',
        columnType: 'quantitative',
        cellRenderer: sizeRenderer,
      },
      wasted_bytes: {
        title: 'Wasted',
        columnType: 'quantitative',
        cellRenderer: sizeRenderer,
      },
    },
  };
}

function makeDuplicateArraySchema(navigate: NavFn): SchemaRegistry {
  return {
    query: {
      className: {
        title: 'Array Type',
        columnType: 'text',
        cellRenderer: (value: SqlValue) =>
          m(
            'button',
            {
              class: 'pf-hde-link',
              onclick: () => navigate('objects', {cls: String(value ?? '')}),
            },
            String(value ?? ''),
          ),
      },
      arrayHash: {
        title: 'Hash',
        columnType: 'text',
      },
      copies: {
        title: 'Copies',
        columnType: 'quantitative',
        cellRenderer: (value: SqlValue, row) =>
          m(
            'button',
            {
              class: 'pf-hde-link',
              onclick: () =>
                navigate('arrays', {
                  arrayHash: String(row.arrayHash ?? ''),
                }),
            },
            String(value),
          ),
      },
      total_bytes: {
        title: 'Total',
        columnType: 'quantitative',
        cellRenderer: sizeRenderer,
      },
      wasted_bytes: {
        title: 'Wasted',
        columnType: 'quantitative',
        cellRenderer: sizeRenderer,
      },
    },
  };
}

function makeDuplicateStringSchema(navigate: NavFn): SchemaRegistry {
  return {
    query: {
      value: {
        title: 'Value',
        columnType: 'text',
        cellRenderer: (value: SqlValue) =>
          m(
            'button',
            {
              class:
                'pf-hde-link pf-hde-mono pf-hde-break-all pf-hde-str-color',
              onclick: () =>
                navigate('strings', {
                  q: String(value ?? ''),
                }),
            },
            '"' +
              (String(value ?? '').length > 200
                ? String(value).slice(0, 200) + '\u2026'
                : String(value ?? '')) +
              '"',
          ),
      },
      copies: {
        title: 'Copies',
        columnType: 'quantitative',
        cellRenderer: (value: SqlValue, row) =>
          m(
            'button',
            {
              class: 'pf-hde-link',
              onclick: () => navigate('strings', {q: String(row.value ?? '')}),
            },
            String(value),
          ),
      },
      total_bytes: {
        title: 'Total',
        columnType: 'quantitative',
        cellRenderer: sizeRenderer,
      },
      wasted_bytes: {
        title: 'Wasted',
        columnType: 'quantitative',
        cellRenderer: sizeRenderer,
      },
    },
  };
}

function renderDuplicateSection(
  title: string,
  groupCount: number,
  totalWasted: number,
  targetView: string,
  linkLabel: string,
  navigate: NavFn,
  schema: SchemaRegistry,
  data: Row[],
  columns: Array<{id: string; field: string}>,
): m.Children {
  return m('div', {class: 'pf-hde-card pf-hde-mt-4'}, [
    m('h3', {class: 'pf-hde-sub-heading'}, title),
    m('p', {class: 'pf-hde-desc'}, [
      groupCount +
        ' group' +
        (groupCount > 1 ? 's' : '') +
        ' detected, wasting ',
      m('span', {class: 'pf-hde-mono pf-hde-semibold'}, fmtSize(totalWasted)),
      '. ',
      m(
        'button',
        {
          class: 'pf-hde-link--alt',
          onclick: () => navigate(targetView as NavState['view']),
        },
        linkLabel,
      ),
    ]),
    m('div', {class: 'pf-hde-dup-grid-container'}, [
      m(DataGrid, {
        schema,
        rootSchema: 'query',
        data,
        initialColumns: columns,
        fillHeight: true,
      }),
    ]),
  ]);
}

interface OverviewViewAttrs {
  readonly overview: OverviewData;
  readonly activeDump: HeapDump;
  readonly navigate: NavFn;
  readonly showDefaultChangedHint: boolean;
  readonly onBackToTimeline: () => void;
  readonly onDismissDefaultChangedHint: () => void;
}
function OverviewView(): m.Component<OverviewViewAttrs> {
  return {
    view(vnode) {
      const {
        overview,
        activeDump,
        navigate,
        showDefaultChangedHint,
        onBackToTimeline,
        onDismissDefaultChangedHint,
      } = vnode.attrs;
      const showHint = showDefaultChangedHint;
      const heapIndices: number[] = [];
      for (let i = 0; i < overview.heaps.length; i++) {
        const h = overview.heaps[i];
        if (h.java + h.native_ > 0) {
          heapIndices.push(i);
        }
      }
      const heaps = heapIndices.map((i) => overview.heaps[i]);
      const totalJava = heaps.reduce((a, h) => a + h.java, 0);
      const totalNative = heaps.reduce((a, h) => a + h.native_, 0);

      const heapRows: Row[] = [
        {
          heap: 'Total',
          java_size: totalJava,
          native_size: totalNative,
          total_size: totalJava + totalNative,
        },
        ...heaps.map((h) => ({
          heap: h.name,
          java_size: h.java,
          native_size: h.native_,
          total_size: h.java + h.native_,
        })),
      ];

      const processLabel =
        (activeDump.processName ?? '<unknown>') +
        (activeDump.pid ? ` (pid ${activeDump.pid})` : '');
      const infoRows: Row[] = [
        {property: 'Process', value: processLabel},
        {property: 'Classes', value: overview.classCount.toLocaleString()},
        {
          property: 'Reachable instances',
          value: overview.reachableInstanceCount.toLocaleString(),
        },
        {
          property: 'Unreachable instances',
          value: overview.unreachableInstanceCount.toLocaleString(),
        },
      ];

      return m('div', {class: 'pf-hde-view-scroll'}, [
        m('h2', {class: 'pf-hde-view-heading'}, 'Overview'),
        showHint
          ? m(
              Callout,
              {
                className: 'pf-hde-default-changed-callout',
                icon: 'info',
                dismissible: true,
                onDismiss: onDismissDefaultChangedHint,
              },
              m('p', [
                m(
                  'span',
                  'Heapdump Explorer is now the default view for traces ' +
                    'with heap-graph data.',
                ),
                m(Button, {
                  label: 'Back to Timeline',
                  icon: 'arrow_back',
                  compact: true,
                  onclick: onBackToTimeline,
                }),
              ]),
            )
          : null,

        m('div', {class: 'pf-hde-card pf-hde-mb-4'}, [
          m('h3', {class: 'pf-hde-sub-heading'}, 'General Information'),
          m(DataGrid, {
            schema: INFO_SCHEMA,
            rootSchema: 'query',
            data: infoRows,
            initialColumns: [
              {id: 'property', field: 'property'},
              {id: 'value', field: 'value'},
            ],
          }),
        ]),
        m('div', {class: 'pf-hde-card'}, [
          m('h3', {class: 'pf-hde-sub-heading'}, 'Bytes Retained by Heap'),
          m(DataGrid, {
            schema: HEAP_SCHEMA,
            rootSchema: 'query',
            data: heapRows,
            initialColumns: [
              {id: 'heap', field: 'heap'},
              {id: 'java_size', field: 'java_size'},
              {id: 'native_size', field: 'native_size'},
              {id: 'total_size', field: 'total_size'},
            ],
          }),
        ]),
        overview.duplicateBitmaps && overview.duplicateBitmaps.length > 0
          ? renderDuplicateSection(
              'Duplicate Bitmaps',
              overview.duplicateBitmaps.length,
              overview.duplicateBitmaps.reduce((a, g) => a + g.wastedBytes, 0),
              'bitmaps',
              'View Bitmaps',
              navigate,
              makeDuplicateBitmapSchema(navigate),
              overview.duplicateBitmaps.map((g) => ({
                dimensions: `${g.width} \u00d7 ${g.height}`,
                groupKey: g.groupKey,
                copies: g.count,
                total_bytes: g.totalBytes,
                wasted_bytes: g.wastedBytes,
              })),
              [
                {id: 'dimensions', field: 'dimensions'},
                {id: 'groupKey', field: 'groupKey'},
                {id: 'copies', field: 'copies'},
                {id: 'total_bytes', field: 'total_bytes'},
                {id: 'wasted_bytes', field: 'wasted_bytes'},
              ],
            )
          : overview.hasFieldValues
            ? m(
                'div',
                {class: 'pf-hde-card pf-hde-mt-4 pf-hde-mb-4'},
                m('p', {class: 'pf-hde-muted'}, 'No duplicate bitmaps found.'),
              )
            : null,
        overview.duplicateStrings && overview.duplicateStrings.length > 0
          ? renderDuplicateSection(
              'Duplicate Strings',
              overview.duplicateStrings.length,
              overview.duplicateStrings.reduce((a, g) => a + g.wastedBytes, 0),
              'strings',
              'View Strings',
              navigate,
              makeDuplicateStringSchema(navigate),
              overview.duplicateStrings.map((g) => ({
                value: g.value,
                copies: g.count,
                total_bytes: g.totalBytes,
                wasted_bytes: g.wastedBytes,
              })),
              [
                {id: 'value', field: 'value'},
                {id: 'copies', field: 'copies'},
                {id: 'total_bytes', field: 'total_bytes'},
                {id: 'wasted_bytes', field: 'wasted_bytes'},
              ],
            )
          : overview.hasFieldValues
            ? m(
                'div',
                {class: 'pf-hde-card pf-hde-mb-4'},
                m('p', {class: 'pf-hde-muted'}, 'No duplicate strings found.'),
              )
            : null,
        overview.duplicateArrays && overview.duplicateArrays.length > 0
          ? renderDuplicateSection(
              'Duplicate Primitive Arrays',
              overview.duplicateArrays.length,
              overview.duplicateArrays.reduce((a, g) => a + g.wastedBytes, 0),
              'arrays',
              'View Arrays',
              navigate,
              makeDuplicateArraySchema(navigate),
              overview.duplicateArrays.map((g) => ({
                className: g.className,
                arrayHash: g.arrayHash,
                copies: g.count,
                total_bytes: g.totalBytes,
                wasted_bytes: g.wastedBytes,
              })),
              [
                {id: 'className', field: 'className'},
                {id: 'arrayHash', field: 'arrayHash'},
                {id: 'copies', field: 'copies'},
                {id: 'total_bytes', field: 'total_bytes'},
                {id: 'wasted_bytes', field: 'wasted_bytes'},
              ],
            )
          : null,
      ]);
    },
  };
}

export default OverviewView;
