import "@typespec/rest";
import "@typespec/http";
import "@typespec/http-client-csharp";
import "@azure-tools/typespec-client-generator-core";
import "@azure-tools/typespec-azure-core";

@versioned(Versions)
@service(#{ title: "hello world" })
@doc("This is a sample typespec project.")
@server(
  "{sampleTypeSpecUrl}",
  "Endpoint Service",
  {
    sampleTypeSpecUrl: url,
  }
)
@useAuth(ApiKeyAuth<ApiKeyLocation.header, "my-api-key"> | SampleOAuth2)
namespace SampleTypeSpec;

using TypeSpec.Http;
using TypeSpec.Versioning;
using TypeSpec.HttpClient.CSharp;
using Azure.ClientGenerator.Core;

alias SampleOAuth2 = OAuth2Auth<[
  {
    type: OAuth2FlowType.implicit;
    authorizationUrl: "https://api.example.com/oauth2/authorize";
    refreshUrl: "https://api.example.com/oauth2/refresh";
    scopes: ["read"];
  }
]>;

enum Versions {
  `2024-07-16-preview`,
  `2024-08-16-preview`,
}

@doc("float fixed enum")
enum FloatFixedEnumWithIntValue {
  One: 1.0,
  Two: 2.0,
  Four: 4.0,
}

@doc("float fixed enum")
enum FloatFixedEnum {
  OneDotOne: 1.1,
  TwoDotTwo: 2.2,
  FourDotFour: 4.4,
}

@doc("int fixed enum")
enum IntFixedEnum {
  One: 1,
  Two: 2,
  Four: 4,
}

@doc("Simple enum")
enum StringFixedEnum {
  One: "1",
  Two: "2",
  Four: "4",
}

@doc("Int based extensible enum")
union IntExtensibleEnum {
  int32,
  One: 1,
  Two: 2,
  Four: 4,
}

@doc("Float based extensible enum")
union FloatExtensibleEnum {
  float32,
  OneDotOne: 1.1,
  TwoDotTwo: 2.2,
  FourDotFour: 4.4,
}

@doc("float fixed enum")
union FloatExtensibleEnumWithIntValue {
  float32,
  One: 1.0,
  Two: 2.0,
  Four: 4.0,
}

@doc("Extensible enum")
union StringExtensibleEnum {
  string,
  One: "1",
  Two: "2",
  Four: "4",
}

@doc("A model with a few properties of literal types")
model Thing {
  @doc("name of the Thing")
  name: string;

  @doc("required Union")
  requiredUnion: string | string[] | int32;

  @doc("required literal string")
  requiredLiteralString: "accept";

  @doc("required nullable string")
  requiredNullableString: string | null;

  @doc("required optional string")
  optionalNullableString?: string | null;

  @doc("required literal int")
  requiredLiteralInt: 123;

  @doc("required literal float")
  requiredLiteralFloat: 1.23;

  @doc("required literal bool")
  requiredLiteralBool: false;

  @doc("optional literal string")
  optionalLiteralString?: "reject";

  @doc("required nullable literal string")
  requiredNullableLiteralString: "someValue" | null;

  @doc("optional literal int")
  optionalLiteralInt?: 456;

  @doc("optional literal float")
  optionalLiteralFloat?: 4.56;

  @doc("optional literal bool")
  optionalLiteralBool?: true;

  @doc("description with xml <|endoftext|>")
  requiredBadDescription: string;

  @doc("optional nullable collection")
  optionalNullableList?: int32[] | null;

  @doc("required nullable collection")
  requiredNullableList: int32[] | null;
}

@doc("A model with a few required nullable properties")
model ModelWithRequiredNullableProperties {
  @doc("required nullable primitive type")
  requiredNullablePrimitive: int32 | null;

  @doc("required nullable extensible enum type")
  requiredExtensibleEnum: StringExtensibleEnum | null;

  @doc("required nullable fixed enum type")
  requiredFixedEnum: StringFixedEnum | null;
}

@doc("this is not a friendly model but with a friendly name")
@friendlyName("Friend")
model NotFriend {
  @doc("name of the NotFriend")
  name: string;
}

@doc("this is a model with a client name")
@clientName("RenamedModel")
model ModelWithClientName {
  @doc("name of the ModelWithClientName")
  otherName: string;
}

model ModelWithEmbeddedNonBodyParameters {
  @doc("name of the ModelWithEmbeddedNonBodyParameters")
  name: string;

  @doc("required header parameter")
  @header
  requiredHeader: string;

  @doc("optional header parameter")
  @header
  optionalHeader?: string;

  @doc("required query parameter")
  @query
  requiredQuery: string;

  @doc("optional query parameter")
  @query
  optionalQuery?: string;
}

@doc("this is a roundtrip model")
model RoundTripModel {
  @doc("Required string, illustrating a reference type property.")
  requiredString: string;

  @doc("Required int, illustrating a value type property.")
  @encode(string)
  requiredInt: int32;

  @doc("Required collection of enums")
  requiredCollection: StringFixedEnum[];

  @doc("Required dictionary of enums")
  requiredDictionary: Record<StringExtensibleEnum>;

  @doc("Required model")
  requiredModel: Thing;

  @doc("this is an int based extensible enum")
  intExtensibleEnum?: IntExtensibleEnum;

  @doc("this is a collection of int based extensible enum")
  intExtensibleEnumCollection?: IntExtensibleEnum[];

  @doc("this is a float based extensible enum")
  floatExtensibleEnum?: FloatExtensibleEnum;

  @doc("this is a float based extensible enum")
  floatExtensibleEnumWithIntValue?: FloatExtensibleEnumWithIntValue;

  @doc("this is a collection of float based extensible enum")
  floatExtensibleEnumCollection?: FloatExtensibleEnum[];

  @doc("this is a float based fixed enum")
  floatFixedEnum?: FloatFixedEnum;

  @doc("this is a float based fixed enum")
  floatFixedEnumWithIntValue?: FloatFixedEnumWithIntValue;

  @doc("this is a collection of float based fixed enum")
  floatFixedEnumCollection?: FloatFixedEnum[];

  @doc("this is a int based fixed enum")
  intFixedEnum?: IntFixedEnum;

  @doc("this is a collection of int based fixed enum")
  intFixedEnumCollection?: IntFixedEnum[];

  @doc("this is a string based fixed enum")
  stringFixedEnum?: StringFixedEnum;

  @doc("required unknown")
  requiredUnknown: unknown;

  @doc("optional unknown")
  optionalUnknown?: unknown;

  @doc("required record of unknown")
  requiredRecordUnknown: Record<unknown>;

  @doc("optional record of unknown")
  optionalRecordUnknown?: Record<unknown>;

  @doc("required readonly record of unknown")
  @visibility(Lifecycle.Read)
  readOnlyRequiredRecordUnknown: Record<unknown>;

  @doc("optional readonly record of unknown")
  @visibility(Lifecycle.Read)
  readOnlyOptionalRecordUnknown?: Record<unknown>;

  @doc("this is a model with required nullable properties")
  modelWithRequiredNullable: ModelWithRequiredNullableProperties;

  @doc("Required bytes")
  requiredBytes: bytes;
}

@doc("A sample dynamic model.")
@dynamicModel
model DynamicModel {
  name: string;
  optionalUnknown?: unknown;
  optionalInt?: int32;
  optionalNullableList?: int32[] | null;
  requiredNullableList: int32[] | null;
  optionalNullableDictionary?: Record<int32> | null;
  requiredNullableDictionary: Record<int32> | null;
  primitiveDictionary: Record<int32>;
  foo: AnotherDynamicModel;
  listFoo: AnotherDynamicModel[];
  listOfListFoo: AnotherDynamicModel[][];
  dictionaryFoo: Record<AnotherDynamicModel>;
  dictionaryOfDictionaryFoo: Record<Record<AnotherDynamicModel>>;
  dictionaryListFoo: Record<AnotherDynamicModel[]>;
  listOfDictionaryFoo: Record<AnotherDynamicModel>[];
}

@doc("Another sample dynamic model.")
@dynamicModel
model AnotherDynamicModel {
  bar: string;
}

union DaysOfWeekExtensibleEnum {
  string,
  Monday: "Monday",
  Tuesday: "Tuesday",
  Wednesday: "Wednesday",
  Thursday: "Thursday",
  Friday: "Friday",
  Saturday: "Saturday",
  Sunday: "Sunday",
}

@route("/hello")
@doc("Return hi")
@get
op sayHi(
  @header headParameter: string,
  @query queryParameter: string,
  @query optionalQuery?: string,
): Thing;

@route("/againHi")
@doc("Return hi again")
@get
@convenientAPI(true)
op helloAgain(
  @header p1: string,
  @body action: RoundTripModel,
  @header contentType: "text/plain",
  @path p2: string,
): RoundTripModel;

@route("/noContentType")
@doc("Return hi again")
@get
@convenientAPI(false)
op noContentType(
  @header p1: string,
  @body action: RoundTripModel,
  @path p2: string,
): RoundTripModel;

@route("/demoHi")
@doc("Return hi in demo2")
@get
@convenientAPI(true)
op helloDemo2(): Thing;

@route("/literal")
@doc("Create with literal value")
@post
@convenientAPI(true)
op createLiteral(@body body: Thing): Thing;

@route("/helloLiteral")
@doc("Send literal parameters")
@get
@convenientAPI(true)
op helloLiteral(@header p1: "test", @path p2: 123, @query p3: true): Thing;

@route("/top")
@doc("top level method")
@get
@convenientAPI(true)
op topAction(@path action: utcDateTime): Thing;

@route("/top2")
@doc("top level method2")
@get
@convenientAPI(false)
op topAction2(): Thing;

@route("/patch")
@doc("top level patch")
@patch
@convenientAPI(true)
op patchAction(@body body: Thing): Thing;

@route("/anonymousBody")
@doc("body parameter without body decorator")
@post
@convenientAPI(true)
op anonymousBody(
  ...Thing,
  @query requiredQueryParam: "someRequiredLiteralQueryParam",
  @header requiredHeader: "someRequiredLiteralHeader",
): Thing;

@route("/friendlyName")
@doc("Model can have its friendly name")
@post
@convenientAPI(true)
op friendlyModel(...NotFriend): NotFriend;

op addTimeHeader(@header("Repeatability-First-Sent") repeatabilityFirstSent?: utcDateTime): void;

@route("/projectedName")
@doc("Model can have its projected name")
@post
@convenientAPI(true)
op projectedNameModel(...ModelWithClientName): ModelWithClientName;

@route("/returnsAnonymousModel")
@doc("return anonymous model")
@post
@convenientAPI(true)
op returnsAnonymousModel(): {
  @body body: {};
};

@get
@route("/unknown-value")
@doc("get extensible enum")
op getUnknownValue(): DaysOfWeekExtensibleEnum;

@doc("When set protocol false and convenient true, then the protocol method should be internal")
@route("internalProtocol")
@post
@convenientAPI(true)
@protocolAPI(false)
op internalProtocol(@body body: Thing): Thing;

@doc("When set protocol false and convenient true, the convenient method should be generated even it has the same signature as protocol one")
@route("stillConvenient")
@get
@convenientAPI(true)
@protocolAPI(false)
op stillConvenient(): void;

@route("/headAsBoolean")
@doc("head as boolean.")
@head
@convenientAPI(true)
op headAsBoolean(@path id: string): void;

@route("/WithApiVersion")
@doc("Return hi again")
@get
@convenientAPI(true)
op WithApiVersion(@header p1: string, @query apiVersion: string): void;

@route("/link")
@doc("List things with nextlink")
@convenientAPI(true)
@list
op ListWithNextLink(): {
  @pageItems
  things: Thing[];

  @nextLink next?: url;
};

@route("/linkString")
@doc("List things with nextlink")
@convenientAPI(true)
@list
op ListWithStringNextLink(): {
  @pageItems
  things: Thing[];

  @nextLink next?: string;
};

@route("/continuation")
@doc("List things with continuation token")
@convenientAPI(true)
@list
op ListWithContinuationToken(@query @continuationToken token?: string): {
  @pageItems
  things: Thing[];

  @continuationToken nextToken?: string;
};

@route("/continuation/header")
@doc("List things with continuation token header response")
@convenientAPI(true)
@list
op ListWithContinuationTokenHeaderResponse(@query @continuationToken token?: string): {
  @pageItems
  things: Thing[];

  @header @continuationToken nextToken?: string;
};

model Page<T> {
  @pageItems items: T[];
}

@route("list/paging")
@doc("List things with paging")
@convenientAPI(true)
@list
op ListWithPaging(): Page<Thing>;

@route("embeddedParameters")
@doc("An operation with embedded parameters within the body")
@post
op EmbeddedParameters(@bodyRoot body: ModelWithEmbeddedNonBodyParameters): void;

@route("dynamicModel")
@doc("An operation with a dynamic model")
@post
op DynamicModelOperation(@body body: DynamicModel): void;

@doc("Base animal with discriminator")
@discriminator("kind")
model Animal {
  @doc("The kind of animal")
  kind: string;

  @doc("Name of the animal")
  name: string;
}

alias PetContent = {
  @doc("Whether the pet is trained")
  trained: boolean;
};

@doc("Pet is a discriminated animal")
model Pet extends Animal {
  kind: "pet";
  ...PetContent;
}

alias DogContent = {
  @doc("The breed of the dog")
  breed: string;
};

@doc("Dog is a specific type of pet with hierarchy building")
@Legacy.hierarchyBuilding(Pet)
model Dog extends Animal {
  kind: "dog";
  ...PetContent;
  ...DogContent;
}

@route("/animals")
interface AnimalOperations {
  @doc("Update a pet as an animal")
  @put
  @route("/pet/as-animal")
  updatePetAsAnimal(@body animal: Animal): Animal;

  @doc("Update a dog as an animal")
  @put
  @route("/dog/as-animal")
  updateDogAsAnimal(@body animal: Animal): Animal;
}

@route("/pets")
interface PetOperations {
  @doc("Update a pet as a pet")
  @put
  @route("/pet/as-pet")
  updatePetAsPet(@body pet: Pet): Pet;

  @doc("Update a dog as a pet")
  @put
  @route("/dog/as-pet")
  updateDogAsPet(@body pet: Pet): Pet;
}

@route("/dogs")
interface DogOperations {
  @doc("Update a dog as a dog")
  @put
  @route("/dog/as-dog")
  updateDogAsDog(@body dog: Dog): Dog;
}

@clientInitialization({
  initializedBy: InitializedBy.individually | InitializedBy.parent,
})
interface Metrics {
  @doc("Get Widget metrics for given day of week")
  @get
  @route("/metrics/widgets/daysOfWeek")
  getWidgetMetrics(@path day: DaysOfWeekExtensibleEnum): {
    numSold: int32;
    averagePrice: float32;
  };
}
