export const description = `
Validation tests for struct types
`;

import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { keysOf } from '../../../../common/util/data_tables.js';
import { ShaderValidationTest } from '../shader_validation_test.js';

export const g = makeTestGroup(ShaderValidationTest);

g.test('no_direct_recursion')
  .desc('Test that direct recursion of structures is rejected')
  .params(u => u.combine('target', ['i32', 'S']))
  .fn(t => {
    const wgsl = `
struct S {
  a : ${t.params.target}
}`;
    t.expectCompileResult(t.params.target === 'i32', wgsl);
  });

g.test('no_indirect_recursion')
  .desc('Test that indirect recursion of structures is rejected')
  .params(u => u.combine('target', ['i32', 'S']))
  .fn(t => {
    const wgsl = `
struct S {
  a : T
}
struct T {
  a : ${t.params.target}
}`;
    t.expectCompileResult(t.params.target === 'i32', wgsl);
  });

g.test('no_indirect_recursion_via_array_element')
  .desc('Test that indirect recursion of structures via array element types is rejected')
  .params(u => u.combine('target', ['i32', 'S']))
  .fn(t => {
    const wgsl = `
struct S {
  a : array<${t.params.target}, 4>
}
`;
    t.expectCompileResult(t.params.target === 'i32', wgsl);
  });

g.test('no_indirect_recursion_via_array_size')
  .desc('Test that indirect recursion of structures via array size expressions is rejected')
  .params(u => u.combine('target', ['S1', 'S2']))
  .fn(t => {
    const wgsl = `
struct S1 {
  a : i32,
}
struct S2 {
  a : i32,
  b : array<i32, ${t.params.target}().a + 1>,
}
`;
    t.expectCompileResult(t.params.target === 'S1', wgsl);
  });

g.test('no_indirect_recursion_via_struct_attribute')
  .desc('Test that indirect recursion of structures via struct members is rejected')
  .params(u =>
    u //
      .combine('target', ['S1', 'S2'])
      .combine('attribute', ['align', 'location', 'size'])
  )
  .fn(t => {
    const wgsl = `
struct S1 {
  a : i32
}
struct S2 {
  @${t.params.attribute}(${t.params.target}(4).a) a : i32
}
`;
    t.expectCompileResult(t.params.target === 'S1', wgsl);
  });

g.test('no_indirect_recursion_via_struct_member_nested_in_alias')
  .desc(
    `Test that indirect recursion of structures via struct members is rejected when the member type
    is an alias that contains the structure`
  )
  .params(u => u.combine('target', ['i32', 'A']))
  .fn(t => {
    const wgsl = `
alias A = array<S2, 4>;
struct S1 {
  a : ${t.params.target}
}
struct S2 {
  a : S1
}
`;
    t.expectCompileResult(t.params.target === 'i32', wgsl);
  });

interface StructureCase {
  code: string;
  valid: boolean;
  f16?: boolean;
}

const kStructureCases: Record<string, StructureCase> = {
  bool: {
    code: `struct S { x : bool }`,
    valid: true,
  },
  u32: {
    code: `struct S { x : u32 }`,
    valid: true,
  },
  i32: {
    code: `struct S { x : i32 }`,
    valid: true,
  },
  f32: {
    code: `struct S { x : f32 }`,
    valid: true,
  },
  f16: {
    code: `struct S { x : f16 }`,
    valid: true,
    f16: true,
  },

  vec2u: {
    code: `struct S { x : vec2u }`,
    valid: true,
  },
  vec3i: {
    code: `struct S { x : vec3i }`,
    valid: true,
  },
  vec4f: {
    code: `struct S { x : vec4f }`,
    valid: true,
  },
  vec4h: {
    code: `struct S { x : vec4h }`,
    valid: true,
    f16: true,
  },

  mat2x2f: {
    code: `struct S { x : mat2x2f }`,
    valid: true,
  },
  mat3x4h: {
    code: `struct S { x : mat3x4h }`,
    valid: true,
    f16: true,
  },

  atomic_u32: {
    code: `struct S { x : atomic<u32> }`,
    valid: true,
  },
  atomic_i32: {
    code: `struct S { x : atomic<i32> }`,
    valid: true,
  },

  array_u32_4: {
    code: `struct S { x : array<u32, 4> }`,
    valid: true,
  },
  array_u32: {
    code: `struct S { x : array<u32> }`,
    valid: true,
  },
  array_u32_not_last: {
    code: `struct S { x : array<u32>, y : u32 }`,
    valid: false,
  },
  array_u32_override: {
    code: `override o : u32;
    struct S { x : array<u32, o> }`,
    valid: false,
  },

  structure: {
    code: `struct S { x : u32 }
    struct T { x : S }`,
    valid: true,
  },
  structure_structure_rta: {
    code: `struct S { x : array<u32> }
    struct T { x : S }`,
    valid: false,
  },

  pointer: {
    code: `struct S { x : ptr<function, u32> }`,
    valid: false,
  },

  texture: {
    code: `struct S { x : texture_2d<f32> }`,
    valid: false,
  },
  sampler: {
    code: `struct S { x : sampler }`,
    valid: false,
  },
  sampler_comparison: {
    code: `struct S { x : sampler_comparison }`,
    valid: false,
  },

  many_members: {
    code: `struct S {
      m1 : u32,
      m2 : i32,
      m3 : vec4f,
      m4 : array<u32, 8>,
      m5 : array<f32>
    }`,
    valid: true,
  },

  trailing_comma: {
    code: `struct S { x : u32, }`,
    valid: true,
  },

  empty: {
    code: `struct S { }`,
    valid: false,
  },

  name_collision1: {
    code: `struct S { x : u32 }
    struct S { x : u32 }`,
    valid: false,
  },
  name_collision2: {
    code: `fn S() { }
    struct S { x : u32 }`,
    valid: false,
  },
  name_collision3: {
    code: `struct S { x : u32 }
    alias S = u32;`,
    valid: false,
  },
  member_collision: {
    code: `struct S { x : u32, x : u32 }`,
    valid: false,
  },
  no_name: {
    code: `struct { x : u32 }`,
    valid: false,
  },
  missing_l_brace: {
    code: `struct S x : u32 }`,
    valid: false,
  },
  missing_r_brace: {
    code: `struct S { x : u32`,
    valid: false,
  },
  bad_name: {
    code: `struct 123 { x : u32 }`,
    valid: false,
  },
  bad_delimiter: {
    code: `struct S { x : u32; y : u32 }`,
    valid: false,
  },
  missing_delimiter: {
    code: `struct S { x : u32 y : u32 }`,
    valid: false,
  },
  bad_member_decl: {
    code: `struct S { x u32 }`,
    valid: false,
  },
};

g.test('structures')
  .desc('Validation tests for structures')
  .params(u => u.combine('case', keysOf(kStructureCases)))
  .fn(t => {
    const testcase = kStructureCases[t.params.case];
    const code = `${testcase.f16 ? 'enable f16;' : ''}
    ${testcase.code}`;
    t.expectCompileResult(testcase.valid, code);
  });
