/**
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import tmp from 'tmp';
import { ReactNativeFixture } from './ReactNativeFixture';
import fs from 'fs';
import { buildDirTree, createFileStructure } from './fsUtils';
import { AbsolutePath, RawCodegenConfig } from '../src/core';

let tmpDir: string = '';

beforeEach(async () => {
  const dir = tmp.dirSync();
  tmpDir = dir.name;
});

afterEach(() => {
  if (expect.getState().assertionCalls != expect.getState().numPassingAsserts) {
    console.log(buildDirTree(tmpDir));
  }
});

function createPackageJSONContent(config: {
  codegenConfig?: RawCodegenConfig;
}) {
  return JSON.stringify(
    {
      name: 'fake-package',
      version: '0.0.1',
      private: false,
      scripts: {},
      dependencies: {},
      harmony: {
        codegenConfig: config.codegenConfig,
      },
    },
    null,
    2
  );
}

function createTurboModuleSpec(name: string) {
  return `
import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport';
import { TurboModuleRegistry } from 'react-native';

interface Spec extends TurboModule {
  runProcedure(): void;
  getBool(arg: boolean): boolean;
  getString(arg: string): string;
  getObject(arg: { x: { y: number } }): Object;
  getArrayOfNumbersAsync(): Promise<number[]>;
}

export default TurboModuleRegistry.get<Spec>("${name}")!;
`;
}

// --------------------------------------------------------------------------------------------------

it("should not generate code for packages that don't specify 'codegenConfig' in their 'package.json'", async () => {
  createFileStructure(tmpDir, {
    harmony: {
      entry: {
        src: {
          main: {
            ets: {
              generated: {},
            },
            cpp: {},
          },
        },
      },
    },
    src: {
      specs: {
        'NativeSampleTurboModuleSpec.ts': createTurboModuleSpec(
          'AppSampleTurboModule'
        ),
      },
    },
    node_modules: {
      'react-native-harmony-sample-package': {
        src: {
          specs: {
            'NativeSampleTurboModuleSpec.ts': createTurboModuleSpec(
              'ThirdPartySampleTurboModule'
            ),
          },
        },
        'package.json': createPackageJSONContent({
          codegenConfig: {
            specPaths: ['./src/specs'],
          },
        }),
      },
    },
    'package.json': createPackageJSONContent({}),
  });

  new ReactNativeFixture(tmpDir).codegenHarmony({
    cppOutputPath: './harmony/entry/src/main/cpp/generated',
    projectRootPath: '.',
  });

  const cppGeneratedDirPath = AbsolutePath.fromSegments(
    tmpDir,
    'harmony',
    'entry',
    'src',
    'main',
    'cpp',
    'generated'
  );
  expect(
    fs.existsSync(
      cppGeneratedDirPath
        .copyWithNewSegment('ThirdPartySampleTurboModule.h')
        .getValue()
    )
  ).toBeTruthy();
});

it('should allow specifying paths to specs in other packages', async () => {
  createFileStructure(tmpDir, {
    harmony: {
      entry: {
        src: {
          main: {
            ets: {
              generated: {},
            },
            cpp: {},
          },
        },
      },
    },
    src: {
      specs: {
        'NativeSampleTurboModuleSpec.ts': createTurboModuleSpec(
          'AppSampleTurboModule'
        ),
      },
    },
    node_modules: {
      'react-native-sample-package': {
        src: {
          specs: {
            'NativeSampleTurboModuleSpec.ts': createTurboModuleSpec(
              'ThirdPartySampleTurboModule'
            ),
          },
        },
        'package.json': createPackageJSONContent({}),
      },
      'react-native-harmony-sample-package': {
        'package.json': createPackageJSONContent({
          codegenConfig: {
            specPaths: ['~/node_modules/react-native-sample-package/src/specs'],
          },
        }),
      },
    },
    'package.json': createPackageJSONContent({}),
  });

  new ReactNativeFixture(tmpDir).codegenHarmony({
    cppOutputPath: './harmony/entry/src/main/cpp/codegen',
    projectRootPath: '.',
  });

  const cppGeneratedDirPath = AbsolutePath.fromSegments(
    tmpDir,
    'harmony',
    'entry',
    'src',
    'main',
    'cpp',
    'codegen'
  );

  expect(
    fs.existsSync(
      cppGeneratedDirPath
        .copyWithNewSegment('ThirdPartySampleTurboModule.h')
        .getValue()
    )
  ).toBeTruthy();
});

describe('module', () => {
  it('should generate glue layer for a simple turbo module in app directory', async () => {
    createFileStructure(tmpDir, {
      harmony: {
        entry: {
          src: {
            main: {
              ets: {
                generated: {},
              },
              cpp: {},
            },
          },
        },
      },
      src: {
        specs: {
          'NativeSampleTurboModuleSpec.ts':
            createTurboModuleSpec('SampleTurboModule'),
        },
      },
      'package.json': createPackageJSONContent({
        codegenConfig: {
          specPaths: ['./src/specs'],
        },
      }),
    });

    const result = new ReactNativeFixture(tmpDir).codegenHarmony({
      cppOutputPath: './harmony/entry/src/main/cpp/generated',
      projectRootPath: '.',
    });

    const cppGeneratedDirPath = AbsolutePath.fromSegments(
      tmpDir,
      'harmony',
      'entry',
      'src',
      'main',
      'cpp',
      'generated'
    );

    expect(
      fs.existsSync(
        cppGeneratedDirPath.copyWithNewSegment('SampleTurboModule.h').getValue()
      )
    ).toBeTruthy();
    expect(
      fs.existsSync(
        cppGeneratedDirPath
          .copyWithNewSegment('SampleTurboModule.cpp')
          .getValue()
      )
    ).toBeTruthy();
    expect(
      fs.existsSync(
        cppGeneratedDirPath
          .copyWithNewSegment('RNOHGeneratedPackage.h')
          .getValue()
      )
    ).toBeTruthy();
  });
});

function createViewComponentSpec(name: string) {
  return `
import { ViewProps, HostComponent } from 'react-native';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
import type { Float } from 'react-native/Libraries/Types/CodegenTypes';

export interface ${name}NativeProps extends ViewProps {
  size: Float;
}

export default codegenNativeComponent<${name}NativeProps>(
  '${name}'
) as HostComponent<${name}NativeProps>;
  `;
}

describe('component', () => {
  it.each(['SampleView', 'someView'])(
    `should generate glue layer for a fabric component (%p)`,
    async (componentName: string) => {
      createFileStructure(tmpDir, {
        harmony: {
          entry: {
            src: {
              main: {
                ets: {
                  generated: {},
                },
                cpp: {},
              },
            },
          },
        },
        src: {
          specs: {
            'SampleNativeViewComponent.ts':
              createViewComponentSpec(componentName),
          },
        },
        'package.json': createPackageJSONContent({
          codegenConfig: {
            specPaths: ['./src/specs'],
          },
        }),
      });

      new ReactNativeFixture(tmpDir).codegenHarmony({
        cppOutputPath: './harmony/entry/src/main/cpp/generated',
        projectRootPath: '.',
      });

      const cppGeneratedDirPath = AbsolutePath.fromSegments(
        tmpDir,
        'harmony',
        'entry',
        'src',
        'main',
        'cpp',
        'generated'
      );

      expect(
        fs.existsSync(
          cppGeneratedDirPath
            .copyWithNewSegment(`${componentName}JSIBinder.h`)
            .getValue()
        )
      ).toBeTruthy();
    }
  );
});