diff --git a/app/page.tsx b/app/page.tsx index 40cccb6..77457db 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -10,6 +10,7 @@ import AircraftForm from "../components/AircraftForm"; import LegalDisclaimerModal from "../components/LegalDisclaimerModal"; import LandingPage from "../components/LandingPage"; import { isPointInPolygon, getCGLimitsAtWeight } from "../utils/calculations"; +import { validateSavedFleet } from "../utils/validation"; export interface CustomStation { id: string; @@ -48,7 +49,14 @@ export default function Home() { useEffect(() => { const loadedFleet = localStorage.getItem("wb_saved_fleet"); if (loadedFleet) { - try { setSavedPlanes(JSON.parse(loadedFleet)); } catch (e) { console.error(e); } + try { + const parsed = JSON.parse(loadedFleet); + const validated = validateSavedFleet(parsed); + setSavedPlanes(validated); + } catch (e) { + console.error("Failed to load saved fleet:", e); + // Optionally clear invalid data or just warn + } } const savedTheme = localStorage.getItem("wb_theme"); if (savedTheme === "dark") { @@ -100,16 +108,17 @@ export default function Home() { reader.onload = (event) => { try { const json = JSON.parse(event.target?.result as string); - if (Array.isArray(json)) { - setSavedPlanes(json); - localStorage.setItem("wb_saved_fleet", JSON.stringify(json)); + try { + const validated = validateSavedFleet(json); + setSavedPlanes(validated); + localStorage.setItem("wb_saved_fleet", JSON.stringify(validated)); alert("Fleet imported successfully!"); setShowSettings(false); - } else { - alert("Invalid file format."); + } catch (validationErr) { + alert(`Import Failed: ${(validationErr as Error).message}`); } } catch (err) { - alert("Error parsing file."); + alert("Import Failed: The file could not be read. Please ensure it is a valid JSON file."); } }; reader.readAsText(file); diff --git a/public/sw.js b/public/sw.js index d7b0292..b03a253 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1 +1 @@ -if(!self.define){let e,s={};const a=(a,n)=>(a=new URL(a+".js",n).href,s[a]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=a,e.onload=s,document.head.appendChild(e)}else e=a,importScripts(a),s()}).then(()=>{let e=s[a];if(!e)throw new Error(`Module ${a} didn’t register its module`);return e}));self.define=(n,i)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let c={};const r=e=>a(e,t),d={module:{uri:t},exports:c,require:r};s[t]=Promise.all(n.map(e=>d[e]||r(e))).then(e=>(i(...e),c))}}define(["./workbox-f1770938"],function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/static/OY9BTVDo_gtYruE727h3x/_buildManifest.js",revision:"745555c29d619878e260d8023b7014c1"},{url:"/_next/static/OY9BTVDo_gtYruE727h3x/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/4bd1b696-deba172d32c79f82.js",revision:"deba172d32c79f82"},{url:"/_next/static/chunks/928-ad31bd01446f2472.js",revision:"ad31bd01446f2472"},{url:"/_next/static/chunks/973-e432f2a884511a8e.js",revision:"e432f2a884511a8e"},{url:"/_next/static/chunks/app/_global-error/page-a3d4f8e832675edd.js",revision:"a3d4f8e832675edd"},{url:"/_next/static/chunks/app/_not-found/page-f7e9e5d73062cbe5.js",revision:"f7e9e5d73062cbe5"},{url:"/_next/static/chunks/app/layout-83a3f45ebc4f840c.js",revision:"83a3f45ebc4f840c"},{url:"/_next/static/chunks/app/legal/page-fe212f5a92cb34b9.js",revision:"fe212f5a92cb34b9"},{url:"/_next/static/chunks/app/page-bcc90e8c733e3c93.js",revision:"bcc90e8c733e3c93"},{url:"/_next/static/chunks/framework-4e51298db41fcfd4.js",revision:"4e51298db41fcfd4"},{url:"/_next/static/chunks/main-app-abcb25e568d7cd6e.js",revision:"abcb25e568d7cd6e"},{url:"/_next/static/chunks/main-c9d818b60410a49e.js",revision:"c9d818b60410a49e"},{url:"/_next/static/chunks/next/dist/client/components/builtin/app-error-a3d4f8e832675edd.js",revision:"a3d4f8e832675edd"},{url:"/_next/static/chunks/next/dist/client/components/builtin/forbidden-a3d4f8e832675edd.js",revision:"a3d4f8e832675edd"},{url:"/_next/static/chunks/next/dist/client/components/builtin/global-error-1dc6a36b040319f2.js",revision:"1dc6a36b040319f2"},{url:"/_next/static/chunks/next/dist/client/components/builtin/not-found-a3d4f8e832675edd.js",revision:"a3d4f8e832675edd"},{url:"/_next/static/chunks/next/dist/client/components/builtin/unauthorized-a3d4f8e832675edd.js",revision:"a3d4f8e832675edd"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-07bf70a7cee35489.js",revision:"07bf70a7cee35489"},{url:"/_next/static/css/bc594687c1a0ae55.css",revision:"bc594687c1a0ae55"},{url:"/_next/static/media/19cfc7226ec3afaa-s.woff2",revision:"9dda5cfc9a46f256d0e131bb535e46f8"},{url:"/_next/static/media/21350d82a1f187e9-s.woff2",revision:"4e2553027f1d60eff32898367dd4d541"},{url:"/_next/static/media/8e9860b6e62d6359-s.woff2",revision:"01ba6c2a184b8cba08b0d57167664d75"},{url:"/_next/static/media/ba9851c3c22cd980-s.woff2",revision:"9e494903d6b0ffec1a1e14d34427d44d"},{url:"/_next/static/media/c5fe6dc8356a8c31-s.woff2",revision:"027a89e9ab733a145db70f09b8a18b42"},{url:"/_next/static/media/df0a9ae256c0569c-s.woff2",revision:"d54db44de5ccb18886ece2fda72bdfe0"},{url:"/_next/static/media/e4af272ccee01ff0-s.p.woff2",revision:"65850a373e258f1c897a2b3d75eb74de"},{url:"/apple-touch-icon.png",revision:"b73ace5d08ca50ae4d207ae49aa324a7"},{url:"/assets/screenshot-calculator.png",revision:"b5fb22478199d5d0a0567d6c3bd2c05d"},{url:"/assets/screenshot-hangar.png",revision:"ebf24315ea400345ca95724c2f38067f"},{url:"/assets/screenshot-report.png",revision:"153a01d7958719252c31e1a52830eb0f"},{url:"/file.svg",revision:"d09f95206c3fa0bb9bd9fefabfd0ea71"},{url:"/globe.svg",revision:"2aaafa6a49b6563925fe440891e32717"},{url:"/icon-192.png",revision:"4cc4ba11db593a4f3899fc2c628e4bfb"},{url:"/icon-512.png",revision:"808fb568cffbb81a989f7e187df3fcc9"},{url:"/manifest.json",revision:"4b7e0c73a2888962193970e2f5bd7149"},{url:"/next.svg",revision:"8e061864f388b47f33a1c3780831193e"},{url:"/swe-worker-5c72df51bb1f6ee0.js",revision:"76fdd3369f623a3edcf74ce2200bfdd0"},{url:"/vercel.svg",revision:"c0af2f507b369b085b35ef4bbe3bcf1e"},{url:"/window.svg",revision:"a2760511c65806022ad20adf74370ff3"}],{ignoreURLParametersMatching:[/^utm_/,/^fbclid$/]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({response:e})=>e&&"opaqueredirect"===e.type?new Response(e.body,{status:200,statusText:"OK",headers:e.headers}):e}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:2592e3})]}),"GET"),e.registerRoute(/\/_next\/static.+\.js$/i,new e.CacheFirst({cacheName:"next-static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp4|webm)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:48,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.NetworkFirst({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({sameOrigin:e,url:{pathname:s}})=>!(!e||s.startsWith("/api/auth/callback")||!s.startsWith("/api/")),new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({request:e,url:{pathname:s},sameOrigin:a})=>"1"===e.headers.get("RSC")&&"1"===e.headers.get("Next-Router-Prefetch")&&a&&!s.startsWith("/api/"),new e.NetworkFirst({cacheName:"pages-rsc-prefetch",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({request:e,url:{pathname:s},sameOrigin:a})=>"1"===e.headers.get("RSC")&&a&&!s.startsWith("/api/"),new e.NetworkFirst({cacheName:"pages-rsc",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:{pathname:e},sameOrigin:s})=>s&&!e.startsWith("/api/"),new e.NetworkFirst({cacheName:"pages",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({sameOrigin:e})=>!e,new e.NetworkFirst({cacheName:"cross-origin",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:3600})]}),"GET"),self.__WB_DISABLE_DEV_LOGS=!0}); +if(!self.define){let e,s={};const a=(a,n)=>(a=new URL(a+".js",n).href,s[a]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=a,e.onload=s,document.head.appendChild(e)}else e=a,importScripts(a),s()}).then(()=>{let e=s[a];if(!e)throw new Error(`Module ${a} didn’t register its module`);return e}));self.define=(n,i)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let c={};const r=e=>a(e,t),d={module:{uri:t},exports:c,require:r};s[t]=Promise.all(n.map(e=>d[e]||r(e))).then(e=>(i(...e),c))}}define(["./workbox-f1770938"],function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/static/chunks/4bd1b696-deba172d32c79f82.js",revision:"deba172d32c79f82"},{url:"/_next/static/chunks/928-34428627dfa9fa40.js",revision:"34428627dfa9fa40"},{url:"/_next/static/chunks/973-e432f2a884511a8e.js",revision:"e432f2a884511a8e"},{url:"/_next/static/chunks/app/_global-error/page-a3d4f8e832675edd.js",revision:"a3d4f8e832675edd"},{url:"/_next/static/chunks/app/_not-found/page-f7e9e5d73062cbe5.js",revision:"f7e9e5d73062cbe5"},{url:"/_next/static/chunks/app/layout-83a3f45ebc4f840c.js",revision:"83a3f45ebc4f840c"},{url:"/_next/static/chunks/app/legal/page-fe212f5a92cb34b9.js",revision:"fe212f5a92cb34b9"},{url:"/_next/static/chunks/app/page-2babf0f8c38c15ee.js",revision:"2babf0f8c38c15ee"},{url:"/_next/static/chunks/framework-4e51298db41fcfd4.js",revision:"4e51298db41fcfd4"},{url:"/_next/static/chunks/main-2682d92e5634a569.js",revision:"2682d92e5634a569"},{url:"/_next/static/chunks/main-app-abcb25e568d7cd6e.js",revision:"abcb25e568d7cd6e"},{url:"/_next/static/chunks/next/dist/client/components/builtin/app-error-a3d4f8e832675edd.js",revision:"a3d4f8e832675edd"},{url:"/_next/static/chunks/next/dist/client/components/builtin/forbidden-a3d4f8e832675edd.js",revision:"a3d4f8e832675edd"},{url:"/_next/static/chunks/next/dist/client/components/builtin/global-error-1dc6a36b040319f2.js",revision:"1dc6a36b040319f2"},{url:"/_next/static/chunks/next/dist/client/components/builtin/not-found-a3d4f8e832675edd.js",revision:"a3d4f8e832675edd"},{url:"/_next/static/chunks/next/dist/client/components/builtin/unauthorized-a3d4f8e832675edd.js",revision:"a3d4f8e832675edd"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-07bf70a7cee35489.js",revision:"07bf70a7cee35489"},{url:"/_next/static/css/7857fc8dde1f0206.css",revision:"7857fc8dde1f0206"},{url:"/_next/static/media/19cfc7226ec3afaa-s.woff2",revision:"9dda5cfc9a46f256d0e131bb535e46f8"},{url:"/_next/static/media/21350d82a1f187e9-s.woff2",revision:"4e2553027f1d60eff32898367dd4d541"},{url:"/_next/static/media/8e9860b6e62d6359-s.woff2",revision:"01ba6c2a184b8cba08b0d57167664d75"},{url:"/_next/static/media/ba9851c3c22cd980-s.woff2",revision:"9e494903d6b0ffec1a1e14d34427d44d"},{url:"/_next/static/media/c5fe6dc8356a8c31-s.woff2",revision:"027a89e9ab733a145db70f09b8a18b42"},{url:"/_next/static/media/df0a9ae256c0569c-s.woff2",revision:"d54db44de5ccb18886ece2fda72bdfe0"},{url:"/_next/static/media/e4af272ccee01ff0-s.p.woff2",revision:"65850a373e258f1c897a2b3d75eb74de"},{url:"/_next/static/w478VczkcDY-SikfBVe2G/_buildManifest.js",revision:"745555c29d619878e260d8023b7014c1"},{url:"/_next/static/w478VczkcDY-SikfBVe2G/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/apple-touch-icon.png",revision:"b73ace5d08ca50ae4d207ae49aa324a7"},{url:"/assets/screenshot-calculator.png",revision:"b5fb22478199d5d0a0567d6c3bd2c05d"},{url:"/assets/screenshot-hangar.png",revision:"ebf24315ea400345ca95724c2f38067f"},{url:"/assets/screenshot-report.png",revision:"153a01d7958719252c31e1a52830eb0f"},{url:"/file.svg",revision:"d09f95206c3fa0bb9bd9fefabfd0ea71"},{url:"/globe.svg",revision:"2aaafa6a49b6563925fe440891e32717"},{url:"/icon-192.png",revision:"4cc4ba11db593a4f3899fc2c628e4bfb"},{url:"/icon-512.png",revision:"808fb568cffbb81a989f7e187df3fcc9"},{url:"/manifest.json",revision:"4b7e0c73a2888962193970e2f5bd7149"},{url:"/next.svg",revision:"8e061864f388b47f33a1c3780831193e"},{url:"/swe-worker-5c72df51bb1f6ee0.js",revision:"76fdd3369f623a3edcf74ce2200bfdd0"},{url:"/vercel.svg",revision:"c0af2f507b369b085b35ef4bbe3bcf1e"},{url:"/window.svg",revision:"a2760511c65806022ad20adf74370ff3"}],{ignoreURLParametersMatching:[/^utm_/,/^fbclid$/]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({response:e})=>e&&"opaqueredirect"===e.type?new Response(e.body,{status:200,statusText:"OK",headers:e.headers}):e}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:2592e3})]}),"GET"),e.registerRoute(/\/_next\/static.+\.js$/i,new e.CacheFirst({cacheName:"next-static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp4|webm)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:48,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.NetworkFirst({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({sameOrigin:e,url:{pathname:s}})=>!(!e||s.startsWith("/api/auth/callback")||!s.startsWith("/api/")),new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({request:e,url:{pathname:s},sameOrigin:a})=>"1"===e.headers.get("RSC")&&"1"===e.headers.get("Next-Router-Prefetch")&&a&&!s.startsWith("/api/"),new e.NetworkFirst({cacheName:"pages-rsc-prefetch",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({request:e,url:{pathname:s},sameOrigin:a})=>"1"===e.headers.get("RSC")&&a&&!s.startsWith("/api/"),new e.NetworkFirst({cacheName:"pages-rsc",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:{pathname:e},sameOrigin:s})=>s&&!e.startsWith("/api/"),new e.NetworkFirst({cacheName:"pages",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({sameOrigin:e})=>!e,new e.NetworkFirst({cacheName:"cross-origin",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:3600})]}),"GET"),self.__WB_DISABLE_DEV_LOGS=!0}); diff --git a/utils/validation.ts b/utils/validation.ts new file mode 100644 index 0000000..13aa2ce --- /dev/null +++ b/utils/validation.ts @@ -0,0 +1,104 @@ +import { SavedAircraft, Station, EnvelopePoint } from "../data/aircraft"; + +/** + * Validates that the input data matches the Station interface + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function validateStation(data: any): data is Station { + if (typeof data !== 'object' || data === null) return false; + + if (typeof data.id !== 'string') return false; + if (typeof data.name !== 'string') return false; + if (typeof data.arm !== 'number') return false; + if (typeof data.maxWeight !== 'number') return false; + + return true; +} + +/** + * Validates that the input data matches the EnvelopePoint interface + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function validateEnvelopePoint(data: any): data is EnvelopePoint { + if (typeof data !== 'object' || data === null) return false; + + if (typeof data.cg !== 'number') return false; + if (typeof data.weight !== 'number') return false; + + return true; +} + +/** + * Validates that the input data matches the SavedAircraft interface + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function validateSavedAircraft(data: any): data is SavedAircraft { + if (typeof data !== 'object' || data === null) return false; + + // Check required string fields + const stringFields = ['id', 'make', 'model', 'registration']; + for (const field of stringFields) { + if (typeof data[field] !== 'string') return false; + } + + // Check required number fields + const numberFields = ['emptyWeight', 'emptyArm']; + for (const field of numberFields) { + if (typeof data[field] !== 'number') return false; + } + + // Check isCustomPlane flag (must be true for SavedAircraft) + if (data.isCustomPlane !== true) return false; + + // Check arrays + if (!Array.isArray(data.stations)) return false; + if (!Array.isArray(data.envelope)) return false; + + // Optional: utilityEnvelope + if (data.utilityEnvelope !== undefined && !Array.isArray(data.utilityEnvelope)) return false; + + // Validate stations + for (const s of data.stations) { + if (!validateStation(s)) return false; + } + + // Validate envelope + for (const pt of data.envelope) { + if (!validateEnvelopePoint(pt)) return false; + } + + // Validate utilityEnvelope if present + if (data.utilityEnvelope) { + for (const pt of data.utilityEnvelope) { + if (!validateEnvelopePoint(pt)) return false; + } + } + + // Validate savedArmOverrides if present + if (data.savedArmOverrides !== undefined) { + if (typeof data.savedArmOverrides !== 'object' || data.savedArmOverrides === null) return false; + for (const key in data.savedArmOverrides) { + if (typeof data.savedArmOverrides[key] !== 'number') return false; + } + } + + return true; +} + +/** + * Validates and casts the input data to SavedAircraft[] + * Throws an error if validation fails + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function validateSavedFleet(data: any): SavedAircraft[] { + if (!Array.isArray(data)) { + throw new Error("The file format is incorrect (expected a list of aircraft)."); + } + + return data.map((item, index) => { + if (!validateSavedAircraft(item)) { + throw new Error(`Aircraft #${index + 1} (${item?.registration || 'Unknown Registration'}) is missing required data or has invalid values.`); + } + return item; + }); +}